在 IPython 重新加载后(避免 TypeError),有什么方法可以手动修复 `super()` 的操作?

Any way to manually fix operation of `super()` after IPython reload (avoiding TypeError)?

这是一个人为的玩具示例,用于触发我遇到的问题:

我有几个 classes,假设它们在本地文件中 '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

我在 IPython 会话中使用它们,可能是这样的:

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()

到目前为止,还不错。但后来我意识到 class A 'fancy-stuff' 中存在一个错误,并在那里进行了一些小的编辑——甚至可能只是添加了一些日志记录。然后我想重复 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

哦不!重新加载 classes 后可怕的 TypeError,而较旧的实例保留了一些较旧的 superclasses!在 SO 和其他地方讨论了这个问题,但没有明确的恢复方法。

但碰巧的是,我真的非常希望能够 运行 在我的旧 c 实例上稍微更新 A.save()。 (我在内存中有 20GB+ 的数据,需要大约一天半的时间才能生成,这些数据将通过 superclass 方法以首选方式保存。我已经通过其他手册保存了足够多的数据我认为我可以在重新启动的 IPython 内核中重建 c 的方法。但是,虽然我仍然拥有真实的对象,但我更愿意进行实际测试补丁 A.save() – 甚至可能在完整的内核重启之前对其进行更多 fixes/tests。)

所以我对任何策略或技巧都感兴趣,无论它们在其他情况下可能多么不明智,强制 c 进入当前 class 定义,一直向上,所以c.save() 就可以了。

有什么想法吗?

我希望适用于这个玩具示例的任何东西都适用于我的真实设置,这是一个基于 CPython 2.7.10 的 IPython)。 (然而,在真实情况下,三个class在不同的文件中。)

您可以将更新后的 class 重新分配给您的实例:

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

此时 self 将再次成为重新加载的 class 层次结构的正确实例。请注意,您也需要在这里重新绑定全局变量;模块重新加载,而不是对 classes.

的全局引用

如果您有一个包含多个模块的更复杂的设置,您需要替换所有对旧 classes 的引用。如果您在以下形式的任何模块中导入:

from some_module import Bar

class Foo(Bar):
    # ...

那么Bar不会在some_module重新加载时重新绑定。您可以通过避免绑定到全局变量来避免强制重新加载和重新绑定依赖项;只绑定模块:

import some_module

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

之后你只需要重新绑定模块对象。或者只是手动重新加载所有涉及的模块,毕竟你的数据存在于你的实例中。

演示:

>>> 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()

在 Python 3 中,使用新的 super().__init__() 语法代替 super(B, self).__init__() 为我解决了类似的问题