两种Singleton单例实现的差异:父类定义_instance是否影响运行逻辑?
SingletonA与SingletonB的实现差异分析
咱们来拆解这两个单例实现的区别,重点聊聊SingletonA预先定义_instance变量带来的运行逻辑差异:
核心区别:_instance的初始化时机
- SingletonA:在类定义阶段就显式声明并初始化了
_instance = None,这个变量是类级别的固定属性,从类创建完成时就存在于类的属性列表中。 - SingletonB:
_instance是在第一次调用__new__方法时,通过hasattr检查发现不存在后,才动态添加到类上的属性。
工作机制的细节差异
SingletonA的运行流程
当第一次创建SingletonA实例时:
- 触发
__new__方法,直接检查cls._instance(此时是预先定义的None) - 因为
None为假值,调用object.__new__(cls)创建新实例,并赋值给cls._instance - 返回这个实例
后续再尝试创建实例时:
- 直接返回已经存在的
cls._instance,不会再执行实例创建逻辑
SingletonB的运行流程
第一次创建SingletonB实例时:
- 触发
__new__方法,通过hasattr(cls, "_instance")检查当前类是否拥有该属性 - 由于类定义时没有
_instance,所以创建新实例并动态给类添加_instance属性 - 返回实例
后续创建实例时:
hasattr检测到_instance已存在,直接返回已有实例
重点:预先定义_instance的关键影响
这是两者最核心的差异,主要体现在子类继承场景,同时还有一些细节区别:
- 子类继承的行为差异
- 对于SingletonA:如果子类没有重写
_instance属性,它会直接继承父类的_instance。这意味着父类一旦创建过实例,子类的__new__会复用父类的实例,导致子类和父类共享同一个实例(这通常不符合单例的预期):class ChildA(SingletonA): pass parent = SingletonA() child = ChildA() print(parent is child) # 输出True,子类和父类共用一个实例 - 对于SingletonB:子类第一次调用
__new__时,hasattr检查的是子类自身的类属性,父类的_instance不会干扰子类。因此子类会创建属于自己的单例实例,父类和子类的实例完全独立:class ChildB(SingletonB): pass parent = SingletonB() child = ChildB() print(parent is child) # 输出False,符合子类独立单例的预期
- 对于SingletonA:如果子类没有重写
- 属性检查的效率与确定性
- SingletonA直接访问
cls._instance,不需要调用hasattr函数,理论上比SingletonB略快(实际业务中这个差异几乎可以忽略)。 - SingletonA的
_instance是类的固定属性,无论是否创建过实例,dir(SingletonA)都会显示这个属性;而SingletonB只有在创建过实例后,_instance才会出现在类的属性列表中。
- SingletonA直接访问
总结
- 如果你的单例需要支持子类继承,并且希望每个子类都拥有独立的单例实例,SingletonB的实现更安全,它通过动态添加类属性避免了子类继承父类
_instance导致的实例共享问题。 - SingletonA的实现更简洁,但在继承场景下会出现意外行为,适合不需要子类化的简单单例场景。
内容的提问来源于stack exchange,提问作者hal
相关产品推荐
相关产品推荐

