4

Here's a contrived toy example for triggering a problem I'm having:

I have several classes, assume they're in a local file 'issue.py':

class A(object):
    def save(self):
        # fancy stuff                                                                                                          
        pass

class B(A):
    def save(self):
        # misc stuff                                                                                                           
        super(B, self).save()

class C(B):
    pass

I use them in an IPython session, perhaps like so:

In [1]: %load_ext autoreload

In [2]: %autoreload 2

In [3]: from issue import A, B, C

In [4]: c = C()

In [5]: c.foo = 'whatever'

In [6]: c.save()

So far, so good. But then I realize there's a bug in the class A 'fancy-stuff', and make a small edit there – maybe even just adding a bit of logging. I then want to repeat the save():

In [7]: c.save()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-6970514bfc33> in <module>()
----> 1 c.save()

/Users/scratch/Documents/dev2015/gensim_venv/src/gensim-develop/docs/notebooks/scratch~/issue.py in save(self)
      7     def save(self):
      8         # misc stuff
----> 9         super(B, self).save()
     10 
     11 class C(B):

TypeError: super(type, obj): obj must be an instance or subtype of type

Oh no!. The dreaded TypeError after reloading classes, while older instances retain some older superclasses! There is discussion of this issue at SO and elsewhere, but no clear recipe for recovery.

But as it so happens, I would really, really like to be able to run the ever-so-slightly updated A.save() on my old c instance. (I've got 20GB+ of data in memory that took about a day and half to generate that would be saved-in-the-preferred-manner by the superclass method. I've saved enough aside via other manual methods that I think I'd be able to reconstruct c in a restarted IPython kernel. But, while I've still got the authentic object around, I'd much prefer to give a realistic test to the patched A.save() – maybe even making more fixes/tests to it before a full kernel restart.)

So I'm interested in any tactics or tricks, no matter how unwise they might be in other situations, for coercing c into the current class definitions, all the way up, so that c.save() just works.

Any ideas?

I expect anything that works for this toy example will work in my real setup, which is a CPython 2.7.10-based IPython). (However, in the real situation, the three classes are in different files.)

gojomo
  • 52,260
  • 14
  • 86
  • 115
  • 1
    Have you attempted to create a new instance of the new `C` and then manually overwrite its data with the data from the old `c`? How or whether this will work depends on what kind of data `c` stores, and how, but that is the first approach that comes to mind. – BrenBarn Jul 12 '15 at 00:55
  • That was something I thought of, too. However, if you try making a `c2` in even the toy example, it gets the same error on `c2.save()` – so its superclass hierarchy isn't fixed. (In my real setup, there's also a similar TypeError affecting C's `__init__` method, which does a super() to B's `__init__`. – gojomo Jul 12 '15 at 00:58

2 Answers2

2

You can re-assign the updated class to your instance:

from issue import A, B, C
c.__class__ = C

at which point self will once again be a proper instance of the reloaded class hierarchy. Notice that you need to re-bind the globals here too; the module reloaded, not the global references to the classes.

If you have a more complex setup with multiple modules, you need to replace all references to the old classes. If you have an import in any module of the form:

from some_module import Bar

class Foo(Bar):
    # ...

then Bar is not going to be re-bound when some_module is reloaded. You can avoid having to forcefully reload and rebind dependencies by avoiding binding to globals; bind only the module instead:

import some_module

class Foo(some_module.Bar):
    # ...

after which you only need to rebind the module objects. Or just manually reload all modules that are involved, your data lives in your instance, after all.

Demo:

>>> class A(object):
...     def save(self):
...         # fancy stuff                                                                                                          
...         pass
... 
>>> class B(A):
...     def save(self):
...         # misc stuff                                                                                                           
...         super(B, self).save()
... 
>>> class C(B):
...     pass
... 
>>> c = C()
>>> c.foo = 'whatever'
>>> c.save()
>>> 
>>> # Re-defining the classes breaks the instance
... 
>>> class A(object):
...     def save(self):
...         # fancy stuff                                                                                                          
...         pass
... 
>>> class B(A):
...     def save(self):
...         # misc stuff                                                                                                           
...         super(B, self).save()
... 
>>> class C(B):
...     pass
... 
>>> isinstance(c, C)
False
>>> c.save()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in save
TypeError: super(type, obj): obj must be an instance or subtype of type
>>> 
>>> # Fixing the issue by rebinding the class
... 
>>> c.__class__ = C
>>> isinstance(c, C)
True
>>> c.save()
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I tried `c.__class__ = C`, but then `c.save()` triggers the exact same error. – gojomo Jul 12 '15 at 00:59
  • 1
    @gojomo: so is `C` in your session still bound to the old class? Then issue another `from issue import A, B, C`. – Martijn Pieters Jul 12 '15 at 00:59
  • Bingo, looks like that did it! Now trying in the real session that inspired the example... – gojomo Jul 12 '15 at 01:01
  • Alas, in the 'real' setup still having a problem. There, B and C are separate files in one module; A is from another. Going to try edits to each file (to be picked up by IPython's change-detection) then explicit imports... – gojomo Jul 12 '15 at 01:06
  • 1
    @gojomo: you are using global imports; `from mod import classname`; if you imported the *module* instead you'd not have to reload the class name in the module either. You probably have to do `moduleB.A = moduleA.A` after the reload, as the `A` reference in the `moduleB` namespace is still bound to the old class. Use `import moduleA` instead and reference `moduleA.A` everywhere. Saves you a lot of re-binding of names. – Martijn Pieters Jul 12 '15 at 01:10
  • Thanks! The idea that even after reload/reimport-at-top-level/`__class__`-reassignment, references in other namespaces lingered and needed patching was the last puzzle-piece in working-through my full problem. (I also hit a bunch of PicklingErrors as I iteratively found & manually re-assigned class refs that were mismatched... but eventually poked things into a state where the 'big save' finished normally.) – gojomo Jul 12 '15 at 02:43
1

In Python 3, using the new super().__init__() syntax instead of super(B, self).__init__() solved a similar problem for me

amolk
  • 1,387
  • 13
  • 13