一、开篇:一个类只生一个“娃”
单例模式(Singleton)是设计模式中最专一的存在——无论你实例化多少次,它永远只返回那一个对象。
现实场景:
-
数据库连接池(全局唯一,节省资源)
-
项目配置文件管理器(全局唯一,避免配置不同步)
-
日志记录器(全局唯一,防止日志文件写入冲突)
今天,我们把 Python 中实现单例的四种正统姿势全部摊开,并深扒构造函数 __new__ 与析构函数 __del__ 的爱恨情仇。
二、必修课:彻底搞懂 __new__ 与 __init__
在写单例前,先理清这两个家伙的区别:
-
__new__(cls):真正的构造函数。负责在内存中分配空间,并返回实例(self)。
-
__init__(self):初始化函数。负责给实例填充数据(属性赋值)。
打个比方:__new__ 是买了一块空地(盖房子),__init__ 是装修房子(贴瓷砖、买家具)。
class Demo:
def __new__(cls):
print("1. 构造中…分配内存")
return super().__new__(cls)
def __init__(self):
print("2. 初始化中…填充数据")
三、姿势①:经典 __new__ 覆写法(最直观)
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True
四、姿势②:类方法获取单例(封装调用)
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
@classmethod
def get_instance(cls):
return cls() # 内部调用 __new__
# 调用更语义化
s = Singleton.get_instance()
五、姿势③:装饰器大法(最 Pythonic)
装饰器可以不改变类的任何代码,凭空给它加上单例能力:
def singleton(cls):
instances = {}
def get_instance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return get_instance
@singleton
class Manager:
pass
m1 = Manager()
m2 = Manager()
print(m1 is m2) # True
优点:符合“开放-封闭”原则,对扩展开放,对修改关闭。
六、姿势④:模块导入法(官方悄悄推荐)
Python 的模块天生就是单例!因为一个 .py 文件在整个解释器生命周期内只会被导入一次。
# mysingleton.py
class _Manager:
def __init__(self): self.data = []
manager = _Manager() # 直接创建好实例
# 其他文件调用
from mysingleton import manager # 无论导入多少次,都是同一个对象
这是 Python 官方文档中推荐的最简单、最可靠的单例实现方式,没有之一!
七、进阶扩展①:多线程下的“双重检查锁”(面试必考)
上面的单例在多线程环境下可能失效(if cls._instance is None 不是原子操作)。Web 后端必须加锁:
import threading
class Singleton:
_lock = threading.Lock()
_instance = None
def __new__(cls):
if cls._instance is None:
with cls._lock: # 加锁保证线程安全
if cls._instance is None: # 双重检查
cls._instance = super().__new__(cls)
return cls._instance
八、进阶扩展②:关于 __del__ 析构函数的血泪教训
在 93.构造函数与析构函数.py 中,我们看到了 __del__。但千万不要依赖它去释放重要资源(如关闭数据库连接)!
原因:
对象存在循环引用时,__del__ 可能永远不会被调用(Python 的垃圾回收器无法决定先删除谁)。
解释器退出时,全局变量可能已被销毁,__del__ 会访问不到必要的数据而报错。
替代方案:显式调用 close() 方法,或者使用上下文管理器(with 语句)。
class Resource:
def __enter__(self):
print("打开资源")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("释放资源(安全可靠)")
with Resource() as r:
pass # 退出 with 块时自动执行 __exit__
💡 终极总结与面试彩蛋
| __new__ 改写 | 直观易懂 | 子类继承需重写 |
| 装饰器 | 不入侵原类,优雅 | 返回的是函数,类型可能改变 |
| 模块导入 | 极简、线程安全、官方推荐 | 不太像“类的实例化” |
| 元类 (Metaclass) | 终极方案,控制类创建过程 | 学习成本高,容易过设计 |
面试高频题:__new__ 和 __init__ 谁先执行?如果 __new__ 返回了一个已有的对象,__init__ 还会执行吗?
答案:__new__ 先执行。如果 __new__ 返回的不是当前 cls 的实例,那么 __init__ 不会被调用。




