Python:派生Singleton类的__init__方法未被调用原因分析
__init__未执行的原因分析 我在测试单例模式与继承的结合时,编写了如下代码:
class Singleton: _instance = None init_attempts = 0 def __init__(self): self.init_attempts += 1 def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance class Derived(Singleton): def __init__(self): super().__init__() self.attribute = "this is derived" def main(): instance_1 = Singleton() instance_2 = Singleton() print("instance_1 and instance_2 are ", end="") if id(instance_1) == id(instance_2): print("the same") else: print("different") derived_instance = Derived() print("derived_instance and instance_2 are the same:", derived_instance is instance_2) try: print(derived_instance.attribute) except AttributeError: print("Initialization of Derived has been prevented") print("Number of initializations:", derived_instance.init_attempts) if __name__ == '__main__': main()
运行后发现Derived类的__init__方法从未被调用,derived_instance.attribute不存在,且derived_instance.init_attempts的值仍为2。我知道SomeClass()会被解析为SomeClass.__call__(),该方法会依次调用__new__和__init__,但这里Derived.__init__并未执行。
运行输出如下:
instance_1 and instance_2 are the same derived_instance and instance_2 are the same: True Initialization of Derived has been prevented Number of initializations: 2
请问这是什么原因?特别是对比某Stack Overflow示例(该示例中Tracer类的__init__被触发两次),我的情况与之不同。我知道单例的继承并非合理实践,也了解装饰器和元类实现单例的方式,我想具体搞清楚继承过程中哪里出了问题,推测可能与方法解析顺序(MRO)有关,但无法解释为何那个示例不受影响。
核心原因解析
1. 类属性共享导致子类复用父类实例
你的单例实现中,_instance是父类Singleton的类属性,而子类Derived没有自己定义_instance,所以会沿着MRO(方法解析顺序)继承并共享父类的_instance:
- 当你先创建
Singleton的实例instance_1和instance_2时,Singleton._instance已经被赋值为Singleton类的实例。 - 当创建
Derived实例时,Derived.__new__会检查cls._instance(此时cls是Derived),但因为Derived没有自己的_instance,会找到父类Singleton的_instance——这个实例已经存在,所以直接返回它。
2. __init__的调用逻辑由实例类型决定
Python中__init__的调用规则是:__call__方法会对__new__返回的实例调用其对应类型的__init__。这里__new__返回的是Singleton类型的实例,所以Python会调用Singleton.__init__,而不会触发Derived.__init__——因为实例的类型是Singleton,它的MRO里没有Derived的__init__。
3. init_attempts值异常的原因
Singleton中的init_attempts是类属性,第一次调用__init__时,self.init_attempts +=1会给实例本身创建一个同名的实例属性(遮蔽类属性)。后续每次调用__init__都会修改这个实例属性,所以instance_1/instance_2的init_attempts最终为2,而derived_instance本质就是这个Singleton实例,所以值也是2。
与你提到的示例差异的原因
你提到的示例中,单例基类的实现确保了每个子类维护自己独立的_instance:
- 它的
__new__会检查当前类是否有自己的_instance,如果没有则创建并绑定到当前类上,而不是共享父类的_instance。 - 因此创建子类实例时,子类的
_instance不存在,会创建新的子类实例,此时__init__会被调用;后续再创建子类实例时,才会复用自己的实例,这就是该示例中__init__被触发两次的原因。
修复思路(可选)
如果要让子类正确实现单例并调用自身__init__,需要让每个子类拥有独立的_instance:
- 方式一:修改父类
__new__,确保给当前类绑定_instance:def __new__(cls, *args, **kwargs): if not hasattr(cls, '_instance'): cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance - 方式二:在每个子类中显式定义
_instance = None,避免共享父类的类属性。
内容的提问来源于stack exchange,提问作者Stefan Hartinger

