ipython: autoreload magic doesn't reload objects
update: the problem appears to be with any object, unrelated to pickle. pickle was just exposing it in a forceful way. Skip to the latest diagnostic https://github.com/ipython/ipython/issues/11588#issuecomment-464949800
when autoreload reloads a python module, it appears to not be fully reloading the already instantiated objects, and pickle fails to pickle them with:
Can't pickle <class 'mytestclass.Test'>: it's not the same object as mytestclass.Test
Here is a notebook to reproduce, must be run in 3 parts (or 3 cells):
# cell1
%reload_ext autoreload
%autoreload 2
import pickle
file = "mytestclass.py"
text = """
class Test():
def __init__(self):
self.ok = 1
"""
with open("mytestclass.py","w") as f: f.write(text)
from mytestclass import *
test = Test()
# works
p = pickle.dumps(test)
# cell2
# touch forces autoreloader to reload the library file
!echo touch $file
!touch $file
# cell3
# now it fails
p = pickle.dumps(test)
The error is:
In [6]: p = pickle.dumps(test)
---------------------------------------------------------------------------
PicklingError Traceback (most recent call last)
<ipython-input-6-740c326b650e> in <module>()
----> 1 p = pickle.dumps(test)
PicklingError: Can't pickle <class 'mytestclass.Test'>: it's not the same object as mytestclass.Test
Please note that it should be run in 3 parts - if it’s run in one go the reload effect won’t be activated. Can be run in jupyter notebook or directly in ipython shell.
I traced why pickle fails and expanded on it in this issue https://github.com/fastai/fastai/issues/1493 - tldr; it compares memory addresses of the object’s class and the class in sys.modules
- if their memory addresses don’t match it refuses to pickle.
I think there are other issues with the incomplete reload, (i.e. object methods disappearing after reload), but I’m yet to make reproducible cases for those. I believe pickle is just exposing a deeper problem.
I tried the git master ipython and went all the way back to ipython-6.0.0 - the problem is the same.
And yes, I read the caveats section, that does say that reload isn’t 100% perfect.
Thank you.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 5
- Comments: 22 (9 by maintainers)
I have looked into this a little. From what I see, only classes and functions within a reloaded module are updated, but instances of these classes are not touched.
Updating these would be relatively straight forward setting
instance.__class__ = new_class
withnew_class
being the updated class reference.Finding these instances is more complicated. The only way I can think of right now is passing
globals()
to the update function and iterate through all objects in the workspace (and recursively through all nested objects like lists, dicts etc.), checking whetherisinstance(obj, old_class)
and then doing the above update.Although I believe this would be the most logical behavior for
%autoreload
, I am not sure of the performance costs.Any other ideas?
So happy you noticed that change, @jdanbrown!
Indeed, both test cases that were failing: https://github.com/ipython/ipython/issues/11588#issuecomment-464949800 https://github.com/ipython/ipython/issues/11588#issue-405929887 no longer fail with ipython-7.9.0.
I’m not sure whether more tests are needed. I will start using the
%autoreload
feature again and see if I notice any more glitches.Thanks, I think it would make sens to try to fix this behavior and have autoreload not break pickle. I’m unsure how easy it would be.
Hmm, was this maybe fixed in 7.9.0 by https://github.com/ipython/ipython/pull/11876? I run into this error all the time in my notebook workflows, and upgrading from ipython 7.7.0 to 7.11.1 appears to have fixed one instance where the error was occurring.
Does anyone have a robust set of repros that we can test?
FWIW, I tested @stas00’s code and it works in IPython 7.2.
Hum, so now I’m unsure. I rarely use it. But if it was doing it then:
Yes, I do not believe autoreload update the current class of existing objects. I’m sure it is possible in Python, but I don’t think we even try to do it. We could look up the MRO and modify it, but that might mean messing around with Ctypes.