单例(Singleton)模式,是一种常用的软件设计模式。使用它,就是为了保证全局环境下只能有一个该类的实例。
通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
**应用场景:**一个项目中多个地方需要读取同一份配置文件,如果每次使用都是导入重新创建实例,读取文件,用完后再销毁,这样做的话,就造成不必要的IO浪费,可以使用单例模式只生成一份配置在内存中。
Python 中理论上来说一个类可以实例化无数个对象,而且每个对象都是相互独立的,都有自己的地址(内存地址),可以使用 id()
方法查看:
class Bar:
pass
a = Bar()
b = Bar()
print(id(a)) # 1474366000712
print(id(b)) # 1474366000880
__new__ 实现
Python 中,一个类创建对象实例是通过调用父类 object
的 __new__(cls)
方法来创建对象的。我们可以通过重写 __new(cls)
方法去实现类只创建一个实例。
class Bar(object):
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance == None:
cls.__instance == object.__new__(cls)
return cls.__instance
else:
return cls.__instance
a = Bar()
b = Bar()
print(id(a)) # 1659937936
print(id(b)) # 1659937936
def singleton(cls):
__instance = {}
def wrapper(*args, **kwargs):
if cls not in __instance:
__instance[cls] = cls(*args, **kwargs)
return __instance[cls]
else:
return __instance[cls]
return wrapper
# if cls not in __instance:
# __instance[cls] = cls(*args, **kwargs)
# return __instance[cls]
@singleton
class Bar:
pass
a = Bar()
b = Bar()
print(id(a)) # 2129789898368
print(id(b)) # 2129789898368
利用的 Python 的模块导入(在同一个 py 文件中使用同一个模块,只会导入一次,后面要再使用就从导入好的里面加载即可)。
s1.py
class My_Singleton(object):
def foo(self):
pass
def bar(self):
pass
my_singleton = My_Singleton()
s2.py
from s1 import my_singleton
a = my_singleton.foo()
b = my_singleton.bar()
print(id(a)) # 1659937936
print(id(b)) # 1659937936
创建实例时把所有实例对象的属性字典:__dict__
指向同一个字典,这样它们具有相同的属性和方法。
class Bar:
def __init__(self, a, b):
self.a = a
self.b = b
a = Bar(2, 3)
b = Bar(4, 5)
print(a.__dict__) # {'a': 2, 'b': 3}
print(b.__dict__) # {'a': 4, 'b': 5}
实例 a 和 b 有在创建时有共同的属性 a 和 b。
class Singleton(object):
_state = {}
def __new__(cls, *args, **kwargs):
ob = super(Singleton, cls).__new__(cls, *args, **kwargs)
ob.__dict__ = cls._state
return ob
class MyClass2(Singleton):
a = 1
优点
实例控制:单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
灵活性:因为类控制了实例化过程,所以类可以灵活更改实例化过程。
缺点
开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
可能的开发混淆:使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用 new 关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
对象生成期:不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于 .NET Framework 的语言),只有单例类能够导致单例类中出现悬浮引用。
所以,我们需要根据自己的需求,是否选择使用单例模式来解决问题。