Python 中如何实现单例模式?
Python 实现单例的几种方式:模块级实例、__new__ 方法、装饰器和元类。本文对比各种实现,重点介绍可复用的元类写法。
单例模式保证一个类全局只有一个实例。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
简单直接,但注意 __init__ 每次实例化都会执行,若初始化有副作用需要加标志位防护。
方法三:模块级单例(最 Pythonic)
# logger.py
class Logger:
...
logger = Logger() # 模块只被导入一次,这个对象天然是单例
使用时 from logger import logger。Python 模块机制保证了导入缓存,绝大多数场景这就够了。
常见问题(FAQ)
Q:多线程下这些实现安全吗?
A:以上写法都不是线程安全的(两个线程可能同时通过 if 检查)。元类方案可以加一把 threading.Lock 保护 _instances 检查与创建过程。
Q:单例有什么缺点?
A:本质是全局状态,会让单元测试互相污染、隐藏依赖关系。使用前考虑是否真的需要——依赖注入通常是更好的设计。
Q:装饰器方式怎么写?
A:把类的实例化包装一层:用一个字典缓存 cls -> instance,每次调用装饰后的类时先查缓存,效果和元类类似但不改变类的继承结构。