单例模式是什么以及如何创建单例模式
1. 何为单例模式
首先解释一下什么是单例模式。
类就像是一台打印机,实例对象就像是由这个打印机打印出来的一份份文件,看起来好像每份文件都一样的,但实际上会由于每份文件的纸张不同等一系列差别,每份文件还是不一样的。
也就是说:
看起来好像由同一个类创建的多个实例是一样的,功能也一样,但实际上是存储在内存上不同位置的对象。
如下所示,写一个最简单的类,分别进行实例化:
class T:
def __init__(self, a):
self.a = a
def test(self):
print(self.a)
>> t1 = T(2)
>> t2 = T(2)
>> t1.test()
2
>> t2.test()
2
>> t1 == t2
False
>> id(t1)
2822587799984
>> id(t2)
2822587506872
如上可看到,即使是由同一个类,创建参数相同的实例,其内存地址的 id 也是不同的。
如果不明白python赋值和引用等原理的,可以看到的这篇文章 变量的引用赋值及深浅拷贝
所以如果在某些场景下,某个类会创建很多个实例,并且实现的方法也是一样的,这样会浪费很多内存空间。
如果不明白类和实例的关系的话,可以看我的这篇文章 一张图解释清楚何为“python一切皆为对象”
2. 通过__new__
构造单例模式
所以可以通过__new__
方法,以及通过一个类变量,控制类的实例化,保证参数相同的实例,有且仅有1个实例对象,减少内存空间:
class T:
INSTANCE_DICT = {}
def __new__(cls, *args):
if cls.INSTANCE_DICT.get(str(args), None):
print(f"已存在已创建的参数为{args}的实例,直接返回该实例")
return cls.INSTANCE_DICT[str(args)]
else:
print(f"还未创建过参数为{args}的实例,新建一个实例")
cls.INSTANCE_DICT[str(args)] = super().__new__(cls)
return cls.INSTANCE_DICT[str(args)]
def __init__(self, a):
self.a = a
def test(self):
print(self.a)
运行如下:
>> t1 = T(1)
还未创建过参数为(1,)的实例,新建一个实例
>> t3 = T(1)
已存在已创建的参数为(1,)的实例,直接返回该实例
>> t2 = T(2)
还未创建过参数为(2,)的实例,新建一个实例
>> t4 = T(2)
已存在已创建的参数为(2,)的实例,直接返回该实例
>> t1 == t2
False
>> t1 == t3
True
>> t4 == t2
True
>> t1.INSTANCE_DICT
{'(1,)': <__main__.T at 0x2912f5fdbe0>, '(2,)': <__main__.T at 0x2912f5fd860>}
这样就是单例模式了。
3. 通过元类meta构造单例模式
class TestMeta(type):
def __init__(cls, *args, **kwargs):
print("meta init")
cls.INSTANCE_DICT = {}
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
print("meta call")
if cls.INSTANCE_DICT.get(str(args), None):
print(f"已存在已创建的参数为{args}的实例,直接返回该实例")
return cls.INSTANCE_DICT[str(args)]
else:
print(f"还未创建过参数为{args}的实例,新建一个实例")
obj = super().__call__(*args, **kwargs)
cls.INSTANCE_DICT[str(args)] = obj
return obj
class T(object, metaclass=TestMeta):
def __new__(cls, *args, **kwargs):
print("new")
print(type(cls))
return super().__new__(cls)
def __init__(self, *args):
print("init")
print(args)
>> t1 = T(1)
meta call
还未创建过参数为(1,)的实例,新建一个实例
new
<class '__main__.TestMeta'>
init
(1,)
>> t2 = T(2)
meta call
还未创建过参数为(2,)的实例,新建一个实例
new
<class '__main__.TestMeta'>
init
(2,)
>> T.INSTANCE_DICT
{'(1,)': <__main__.T at 0x2ddd191a080>, '(2,)': <__main__.T at 0x2ddd19118d0>}
>> t3 = T(1)
meta call
已存在已创建的参数为(1,)的实例,直接返回该实例
>> T.INSTANCE_DICT
{'(1,)': <__main__.T at 0x2ddd191a080>, '(2,)': <__main__.T at 0x2ddd19118d0>}
>> t1 == t3
True