如何以既优雅又安全(关于 Python 模块重新加载)的方式使用 super() ?

How can I use super() in both elegant and safe (regarding Python module reloading) way?

我发现 reload()(Python 2 内置和来自 importlib)的烦人行为,我正在尝试绕行。

我正在交互式 Python 解释器中分析数据。我的代码按模块组织(Python 2 和 3 兼容),我经常更改这些模块。

由于加载数据时间长,重启解释器不可行,所以我更喜欢递归地重新加载模块。

问题是reload() updates the code but preserves the module global scope (it applies to Python 3 importlib.reload())。它似乎对使用 super() 的方法有害(我花了一段时间才意识到发生了什么)。

模块的最小失败示例 bar.py:

class Bar(object):
    def __init__(self):
        super(Bar, self).__init__()

是:

>>> import bar
>>> class Foo(bar.Bar):
...     pass
... 
>>> reload(bar)
<module 'bar' from '[censored]/bar.py'>
>>> Foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "[censored]/bar.py", line 3, in __init__
    super(Bar, self).__init__()
TypeError: super(type, obj): obj must be an instance or subtype of type

我可能会:

  1. use super() without arguments in Python 3 manner(这不是 兼容 Python 2),
  2. 放弃它并改为调用 Bar.__init__(self)这更难 维护discouraged),
  3. monkey-patch class 添加一个包含循环的 class 属性 引用 class 本身。

None 个我喜欢的想法。还有其他方法可以解决这个问题吗?

你不能,因为在涉及模块重新加载的地方基本上不可能优雅和安全地做任何事情。这种事情很难做对。

这里问题的具体体现是Foo的superclass仍然是旧的Bar,但是旧的Bar通过名称,Bar 名称现在指的是它所查找的名称空间中的新 Bar。旧 Bar 正试图将新 Bar 传递给 super


我可以给你一些选择。所有这些都是不优雅不安全并且可能最终会给你奇怪的惊喜,但所有这些模块重新加载也是如此。

可能最容易理解的选项是重新运行 Foo class 定义以获得新的 Foo class 从新的 Bar 下降:

reload(bar)
class Foo(bar.Bar):
    pass

Foo 的新实例将使用新的 Bar,并且不会遇到 super 的问题。旧实例将使用旧 Foo 和旧 Bar。他们不会遇到 __init__ 的问题,因为那已经 运行,但他们可能会遇到其他问题。

您可以采取的另一个选择是 更新 Foo 的超级 class 所以 Foo 现在是新的 [=13] 的后代=]:

reload(bar)

Foo.__bases__ = (bar.Bar,)

Foo 的新旧实例将使用新的 Bar。根据您在 Bar 中所做的更改,新 class 可能与旧实例不兼容,特别是如果您更改了 Bar 在其实例上保留的数据或 Bar.__init__ 的方式执行初始化。

听起来像是您真正希望它重新import 模块所做的事情。以下允许(在 Python 2 和 3 中)。

import bar
import sys

class Foo(bar.Bar):
    pass

# Manually reload module.
_saved_reference = sys.modules['bar']  # Needed for Python 2.
del sys.modules['bar']

import bar  # Re-import instead of reload().
Foo()  # No error.