python实现单例模式的5种方法
单例模式是一个软件的设计模式,为了保证一个类,无论调用多少次产生的实例对象,都是指向同一个内存地址,仅仅只有一个实例(只有一个对象)。
实现单例模式的手段有很多种,但总的原则是保证一个类只要实例化一个对象,下一次再实例的时候就直接返回这个对象,不再做实例化的操作。所以这里面的关键一点就是,如何判断这个类是否实例化过一个对象。
本文讨论python实现单例模式的5种方法。
1. 使用模块
模块天然就是单例的,因为模块只会被加载一次,加载之后,其他脚本里如果使用import 二次加载这个模块时,会从sys.modules里找到已经加载好的模块,模块里的对象天然就是单例的,即使是在多线程环境下也是如此。
编写脚本my_singleton.py
class Singleton(): def __init__(self, name): self.name = name def do_something(self): pass singleton = Singleton('模块单例')
在其他脚本里
from my_singleton import singleton
在任何引用singleton的脚本里,singleton都是同一个对象,这就确保了系统中只有一个Singleton的实例。
这种方法是官方所推荐的,它简单,代码编写容易,不需要考虑多线程的问题,当然,你也可以挑刺说在其他脚本里,还是可以主动的创建Singleton的实例对象。没错,你想搞破坏,总是能找到方法,但设计模式的目的不是为了防止人为搞破坏,而是让系统解耦,让系统易于理解和工作。
2. 使用装饰器
编写一个单例模式的装饰器,来装饰那些需要支持单例的类
def Singleton(cls): instance = {} def _singleton_wrapper(*args, **kargs): if cls not in instance: instance[cls] = cls(*args, **kargs) return instance[cls] return _singleton_wrapper @Singleton class SingletonTest(object): def __init__(self, name): self.name = name slt_1 = SingletonTest('第1次创建') print(slt_1.name) slt_2 = SingletonTest('第2次创建') print(slt_1.name, slt_2.name) print(slt_1 is slt_2)
程序输出结果
第1次创建
第1次创建 第1次创建
True
创建slt_2 对象时,instance 字典中已经存在SingletonTest 这个key,因此直接返回了第一次创建的对象,slt_1 和 slt_2 是同一个对象。
在多线程环境下,这种设计方法是不安全的,多个线程同时判断cls是否在instance字典中,得到的返回结果都是False,于是这些线程都会去创建对象,为了避免这种情况,加上一把RLock锁
from threading import RLock single_lock = RLock() def Singleton(cls): instance = {} def _singleton_wrapper(*args, **kargs): with single_lock: if cls not in instance: instance[cls] = cls(*args, **kargs) return instance[cls] return _singleton_wrapper
3. 使用类
class Singleton(object): def __init__(self, name): self.name = name @classmethod def instance(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance single_1 = Singleton.instance('第1次创建') single_2 = Singleton.instance('第2次创建') print(single_1 is single_2) # True
instance方法会先检查是否存在类属性_instance, 如果不存在,则创建对象,并返回。这个设计虽然实现了单例模式,但在多线程环境下不安全,多个线程同时检查Singleton类是否拥有_instance属性,得到的结果是否False, 则这些线程都会执行对象的创建工作,最后创建出来的对象才是最终的对象,为了在多线程环境下保证数据安全,在需要并发枷锁的地方加上RLock锁
from threading import RLock class Singleton(object): single_lock = RLock() def __init__(self, name): self.name = name @classmethod def instance(cls, *args, **kwargs): with Singleton.single_lock: if not hasattr(Singleton, "_instance"): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance
4. 基于__new__方法实现
__new__方法是构造函数,是真正的用来创建对象的,那么在创建对象的时候进行控制,不是更方便么
from threading import RLock class Singleton(object): single_lock = RLock() def __init__(self, name): self.name = name def __new__(cls, *args, **kwargs): with Singleton.single_lock: if not hasattr(Singleton, "_instance"): Singleton._instance = object.__new__(cls) return Singleton._instance single_1 = Singleton('第1次创建') single_2 = Singleton('第2次创建') print(single_1.name, single_2.name) # 第2次创建 第2次创建 print(single_1 is single_2) # True
这种方法同样实现了多线程环境下的安全的单例模式,但有一个小小的问题,第二次创建对象时,虽然返回了单例,但是修改了单例的name属性,前面已经介绍的三个方法,一旦单例对象被创建,此后的创建都返回第一次创建的对象且不会修改对象的属性。
之所有这样的差别,是因为前面三个方法,都只调用__new__方法一次,__new__方法创建对象后,会调用一次__init__来初始化对象,而方法4会多次调用__new__方法,虽然每次返回的都是同一个对象,但是会立即调用__init__方法,这样就修改了name属性,如果不希望name属性在多次创建对象过程中被修改,只需要做一个小小的修改即可
def __init__(self, name): if hasattr(self, 'name'): return self.name = name
5. 使用元类
from threading import RLock class SingletonType(type): single_lock = RLock() def __call__(cls, *args, **kwargs): # 创建cls的对象时候调用 with SingletonType.single_lock: if not hasattr(cls, "_instance"): cls._instance = super(SingletonType, cls).__call__(*args, **kwargs) # 创建cls的对象 return cls._instance class Singleton(metaclass=SingletonType): def __init__(self, name): self.name = name single_1 = Singleton('第1次创建') single_2 = Singleton('第2次创建') print(single_1.name, single_2.name) # 第1次创建 第1次创建 print(single_1 is single_2) # True
在5种python实现单例模式的方法中,基于元类的实现,可能是最难理解的一个。
class Singleton(metaclass=SingletonType) 这行代码定义了一个类,这个类是元类SingletonType 的实例,是元类SingletonType的__new__构造出来的,Singleton是实例,那么Singleton('第1次创建')就是在调用元类SingletonType 的__call__方法,__call__方法可以让类的实例像函数一样去调用。
在__call__方法里,cls就是类Singleton,为了创建对象,使用super来调用__call__方法,而不能直接写成cls(*args, **kwargs), 这样等于又把SingletonType的__call__方法调用了一次,形成了死循环。
感谢分享:python 6种方法实现单例模式
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现