简介:python单例,不同方式的注意事项
什么是单例
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
Python的单例实现有三种方式,最不同的就是通过 __new__ 魔法方法来实现,这种方式类似与PHP中的单例的实现。除此之外还可以通过装饰器以及元类实现单例
1. 通过___new___实现单例
class Instance():
_instance = None
def __init__(self):
print('初始化')
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
a = Instance()
c = Instance()
print(id(a))
print(id(c))
代码执行打印结果:
初始化
初始化
2889223725568
2889223725568
从打印的结果可以看出,这种方式实现的单例会调用__init__魔术方法多次,但是最总实例化的对象的指针id是同一个,表明他们指向同一个对象。
2. 通过装饰器实现单例(推荐)
既然是通过装饰器实现单例,那我们就必须先定义一个装饰器函数:
def singleton(cls):
instances = {}
def getinstance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return getinstance
这个装饰器,我们可以把它封装到某个公共文件中,以提高单例装饰器的复用性。
接着就可以在任意类上面使用装饰器来表明这是一个单例即可,具体代码方式如下:
@singleton
class MyClass:
def __init__(self):
print('初始化')
a = MyClass()
b = MyClass()
print(id(a))
print(id(b))
上述代码执行打印结果如下:
初始化
1203478770160
1203478770160
从上面的结果就可以看出,实例化的对象a及b的内存地址id是相同的,表示他们是同一个对象。此外,这种方式实现的单例只会调用一次 __init__ 魔术方法,这是与第一种方式实现的单例完全不同。
装饰器实现单例时不能使用类变量,否则会直接报错。例如下方代码:
@singleton
class MyClass:
# 类属性
frame_processor = None
def __init__(self):
print('初始化')
def get_frame_processor(self):
return MyClass.frame_processor
a = MyClass()
a.get_frame_processor()
使用装饰器实现单例后,get_frame_processor对象方法内无法通过MyClass获取类属性,否则会报错:'function' object has no attribute 'frame_processor'
3. 通过元类实现单例
元类方式结合了第一种和装饰器的两种方式。只不过装饰器变成了一个基类,具体代码如下:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
关于这个基类,我们可以和装饰器一样封装到一个公共的文件中。
子类如果想要实现单例,就必须继承 Singleton 这个基类,并且是以元类的方式继承。具体实现方式如下:
class MyClass(metaclass=Singleton):
def __init__(self):
print('初始化')
a = MyClass()
b = MyClass()
print(id(a))
print(id(b))
上述代码执行的结果如下:
初始化
2270477843856
2270477843856
执行结果和装饰器的一致,没有什么区别。但是日常工作我个人建议大家还是使用装饰器来实现单例,毕竟自己实现的类可能需要继承其他类,如果以元类的方式实现,就会显得代码比较臃肿,我个人不太喜欢。
有遗漏或者不对的可以在我的公众号留言哦