【问题标题】:How can I use super() in both elegant and safe (regarding Python module reloading) way?如何以优雅和安全(关于 Python 模块重新加载)的方式使用 super()?
【发布时间】:2018-04-16 15:07:48
【问题描述】:

我发现reload()(Python 2 内置和来自importlib)的一个令人讨厌的行为,我正试图绕过它。

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

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

问题在于reload() updates the code but preserves the module global scope(它也适用于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)which is harder to maintaindiscouraged),
  3. monkey-patch 类添加包含循环的类属性 引用类本身。

没有我喜欢的想法。有没有其他办法处理这个问题?

【问题讨论】:

  • 您的bar.py 已经失败,因为您将super(Bar, self).__init__() 放在类级别而不是__init__ 方法中。
  • 您对super的使用实际上是无效的。 super 用于不在类主体中的类的方法中。
  • 另外,一般来说,重新加载模块真的很难做到,reload 只处理最简单的情况。最好的选择永远是从头开始重新启动 Python。
  • 糟糕,我在输入示例时漏掉了一行。
  • @bphi:永远不要这样做,因为一旦您将该类子类化并且self.__class__ 是子类,它就会中断。如果就这么简单,super 一开始就不会需要类型参数。

标签: python python-2.7 python-3.x python-import python-module


【解决方案1】:

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

问题在这里表现出来的具体方式是Foo的超类仍然是旧的Bar,但是旧的Bar通过名称引用自己,而Bar的名称现在引用新的Bar 在它正在查找的名称空间中。旧的Bar 正在尝试将新的Bar 传递给super


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

可能最容易理解的选项是重新运行Foo 类定义,以从新的Bar 得到一个新的Foo 类:

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

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

您可以采取的另一个选择是更新Foo 的超类,这样Foo 现在就继承自新的Bar

reload(bar)

Foo.__bases__ = (bar.Bar,)

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

【讨论】:

  • 在我现实生活中的示例模块中递归地重新加载它们的导入,所以我希望不会出现代码差异。
【解决方案2】:

听起来像你真正想要它做的事情 re-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.

【讨论】:

  • _saved_reference 是干什么用的?
  • 这个解决方案当然有它自己的奇怪的惊喜。对barBar 的旧引用仍将引用旧模块和旧类,因此Foo 仍将使用旧类,并且已导入bar 的任何其他模块仍将具有旧模块.以这种方式重新导入包也可能会破坏对子模块的访问。如果不重新启动 Python,任何选项都会有奇怪的惊喜。
  • @user2357112: _saved_reference 是必需的,因为 Python 2 做了一些事情。请参阅我接受的对this question 的讨论。
  • 嗯。我认为这是解释器关闭的影响,而不是模块析构函数的一部分。
猜你喜欢
  • 2015-03-18
  • 2013-07-31
  • 2017-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-15
  • 2010-10-01
相关资源
最近更新 更多