You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 21:15:23