欢迎光临
我们一直在努力

《单例模式的“四世同堂”:从 new 到装饰器再到模块,总有一种写法能让你面试吹牛》

一、开篇:一个类只生一个“娃”

单例模式(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__ 不会被调用。

    赞(0)
    未经允许不得转载:171主机测评 » 《单例模式的“四世同堂”:从 new 到装饰器再到模块,总有一种写法能让你面试吹牛》
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址