Python 中如何实现单例模式?

Python 实现单例的几种方式:模块级实例、__new__ 方法、装饰器和元类。本文对比各种实现,重点介绍可复用的元类写法。

最佳实践
Python 中如何实现单例模式?封面

以下单例模式把普通构造/导入路径中的实例限制在单个解释器进程内,并非跨进程全局唯一。Python 里最"Pythonic"的方式其实是不写模式——直接用模块级对象;需要类形态时,元类(metaclass)方案最通用:控制 __call__,让每次实例化都返回同一个对象。

方法一:元类(可复用,推荐)

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Logger(metaclass=Singleton):
    pass

a = Logger()
b = Logger()
print(a is b)  # True

实例化类时实际调用的是元类的 __call__,在这里拦截并缓存实例,任何类加上 metaclass=Singleton 就变成单例。

方法二:重写 new

class Logger:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

简单直接,但子类可能继承父类的 _instance 缓存,不能直接保证每个子类独立实例;注意 __init__ 每次实例化都会执行,若初始化有副作用需要加标志位防护。

方法三:模块级单例(最 Pythonic)

# logger.py
class Logger:
    ...

logger = Logger()   # 普通导入复用 sys.modules 缓存;reload 或不同导入身份会重新执行

使用时 from logger import logger。Python 模块机制保证了导入缓存,绝大多数场景这就够了。

常见问题(FAQ)

Q:多线程下这些实现安全吗?
A:前两个显式检查/创建缓存的示例不是线程安全的;普通模块导入由导入机制协调,但对象自身操作仍需线程安全。元类方案可以加一把 threading.Lock 保护 _instances 检查与创建过程。

Q:单例有什么缺点?
A:本质是全局状态,会让单元测试互相污染、隐藏依赖关系。使用前考虑是否真的需要——依赖注入通常是更好的设计。

Q:装饰器方式怎么写?
A:把类的实例化包装一层:用一个字典缓存 cls -> instance,每次调用装饰后的类时先查缓存,常见装饰器会把类名替换为包装函数,影响 isinstance、继承和类型身份,并不等价于保留类的元类方案。

[1]: 观测云文档《DataKit 文档》https://docs.guance.com/datakit/

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台