修改sys.modules[__name__]后调用父类方法报错的技术问询
这个问题的核心原因是当你将sys.modules[__name__]替换为Child实例后,原模块对象失去了唯一引用,被Python的垃圾回收机制销毁,导致模块级别的Child和Parent类定义也被回收。当你调用module1.my_method()时,Child实例的方法试图引用全局作用域中的Child类(也就是super(Child, self)里的Child),但此时这个类已经不存在了,从而引发报错。
为什么会这样?
Python加载模块时,会创建一个模块对象并将其存入sys.modules。当你执行sys.modules[__name__] = Child(__name__),你替换了sys.modules中该模块的条目为Child实例,原模块对象因为没有其他引用会被GC回收。而模块中定义的Parent、Child类都属于原模块对象的全局命名空间,会随模块对象销毁而消失。此时Child实例的my_method方法在调用super(Child, self)时,找不到Child类的引用,就会抛出错误。
解决方案
这里提供几种可行的修复方案:
方案1:直接调用父类方法,不使用super
既然super需要依赖类的引用,我们可以绕过super,直接显式调用父类的方法并传入self:
class Parent(object): def my_method(self): print 'Hello World' class Child(Parent): def __init__(self, name): self.name = name def my_method(self): # Do something Parent.my_method(self) # 直接调用父类方法 # Do something more import sys sys.modules[__name__] = Child(__name__)
这种方式不需要依赖Child类的引用,直接通过父类调用,避免了全局类引用被回收的问题。
方案2:将类引用保存到实例属性中
我们可以在Child实例中保存Child类的引用,这样即使原模块对象被回收,实例依然持有类的引用,super调用就能正常工作:
class Parent(object): def my_method(self): print 'Hello World' class Child(Parent): def __init__(self, name): self.name = name self._child_class = Child # 保存类引用到实例属性 def my_method(self): # Do something super(self._child_class, self).my_method() # 使用实例中保存的类引用 # Do something more import sys sys.modules[__name__] = Child(__name__)
这样super调用依赖的是实例持有的类引用,不会受原模块对象回收的影响。
方案3:保留原模块对象的引用
我们可以给原模块对象添加一个引用,防止被GC回收,这样模块级别的类定义就会保留:
class Parent(object): def my_method(self): print 'Hello World' class Child(Parent): def __init__(self, name): self.name = name def my_method(self): # Do something super(Child, self).my_method() # Do something more import sys original_module = sys.modules[__name__] # 保存原模块对象的引用 sys.modules[__name__] = Child(__name__) original_module.child_instance = sys.modules[__name__] # 让原模块引用新的实例
这样原模块对象因为被original_module变量引用,不会被回收,模块中的Child和Parent类也会保留,super调用就能正常执行。
额外说明
如果你的项目可以升级到Python 3,使用无参数的super()会更简洁且不易出错,因为Python 3的super()会自动获取当前类和实例的信息,不需要显式传递参数:
class Parent: def my_method(self): print('Hello World') class Child(Parent): def __init__(self, name): self.name = name def my_method(self): # Do something super().my_method() # Python 3的无参数super # Do something more import sys sys.modules[__name__] = Child(__name__)
内容的提问来源于stack exchange,提问作者Vinayak Kaniyarakkal

