一、介绍
单例模式是一种常见的设计模式,它保证一个类只能被实例化一次,并提供了一个全局访问点来获取这个唯一的实例。在Python中,可以通过使用装饰器、元类或模块等方式实现单例模式。
二、Python实现单例模式的6种方法
1、使用模块实现单例
python
class Singleton(object):
def foo(self):
pass
singleton = Singleton()
python
from mysingleton import singleton
a = singleton
b = singleton
print(id(a))
print(id(b))
2、通过装饰器实现单例
python
def singleeton_func(cls):
instance={}
def _singleton(*args, **kwargs):
if cls not in instance:
instance[cls] = cls(*args, **kwargs)
return instance[cls]
return _singleton
@singleeton_func
class Phone(object):
def phone_id(self):
return id(self)
if __name__ == '__main__':
p1 = Phone()
p2 = Phone()
print(p1.phone_id())
print(p2.phone_id())
在装饰器的内函数中,判断字典是否已经有键值对。如果没有,则添加一个类和类实例的键值对,如果有,则不添加。最后返回字典中的类实例,可以保证每次返回的都是同一个实例。要使用这个单例装饰器,只要将其装饰到需要实现单例的类上即可。
3、使用实例化方式实现单例
python
class SingletonInstance(object):
def __call__(self, *args, **kwargs):
return self
if __name__ == '__main__':
SingletonInstance = SingletonInstance()
s1 = SingletonInstance()
s2 = SingletonInstance()
print(id(s1))
print(id(s2))
在类中,先重写类的 call 方法,在 call 方法中返回自己。先实例化一个类的对象,后面所有需要使用这个类的地方,都调用这个实例对象。这样,每次调用的都是同一个实例,所以也能实现单例。
4、使用类装饰器实现单例
python
class SingletonDecorator(object):
_instance = None
def __init__(self, cls):
self._cls = cls
def __call__(self, *args, **kwargs):
if self._instance is None:
self._instance = self._cls(*args, **kwargs)
return self._instance
@SingletonDecorator
class Phone(object):
def phone_id(self):
return id(self)
if __name__ == '__main__':
p1 = Phone()
p2 = Phone()
print(p1.phone_id())
print(p2.phone_id())
_var:命名约定,仅供内部使用。通常不会由Python解释器强制执行。(私有变量)
var_:按约定使用以避免与Python关键字的命名冲突。
__var:当在类上下文中使用时,触发"名称修饰"。由Python解释器强制执行。
var:表示Python语言定义的特殊方法。避免在你自己的属性中使用这种命名方案。
使用装饰器实现单例,因为装饰器除了可以使用闭包实现,还可以使用类实现,所以也可以使用类装饰器来实现单例。通过重写类的 call 方法实现类装饰器,在 call 方法中判断当前是否有类实例,没有才会创建,从而实现单例。
5、重写类的__new__方法实现单例
python中的super()详解参考python中的super调用父类方法
python
class SingletonClass(object):
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
# cls._instance = super(SingletonClass, cls).__new__(cls) # python2.x中,super()函数的使用语法格式
cls._instance = super().__new__(cls) # python3.x中,super()函数的使用语法格式
return cls._instance
_is_init = False
def __init__(self):
if self._is_init is False:
print('-*-')
self._is_init = True
if __name__ == '__main__':
s1 = SingletonClass()
s2 = SingletonClass()
print(id(s1))
print(id(s2))
在Python类中,每次实例化一个类对象时,都会自动先执行__new__方法和__init__方法。
__new__方法先在内存中为实例对象申请空间,然后__init__方法初始化实例对象。因为__init__方法是在new执行完成后自动执行的,每次实例类的对象时都会执行__init__方法,所以也要对__init__方法进行重写,只有第一次实例化类对象时才执行初始化操作。
通过重写__new__方法,如果这个类没有实例对象,则执行__new__,有则返回已有的实例,从而实现单例。
6、通过元类(metaclass)实现单例
元类详解参考Python中的元类
python
class SingletonMeta(type, object):
def __init__(self, *args, **kwargs):
self._instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self._instance is None:
self._instance = super().__call__(*args, **kwargs)
return self._instance
class Phone(object, metaclass=SingletonMeta):
def phone_id(self):
return id(self)
if __name__ == '__main__':
p1 = Phone()
p2 = Phone()
print(p1.phone_id())
print(p2.phone_id())
上述代码中,我们定义了一个名为SingletonMeta的元类。在元类的__call__方法中,我们首先判断该类是否已经存在于instances字典中,如果不存在,则创建一个新的实例并将其添加到instances字典中,否则返回已有的实例。
在python中,元类是创建类的类,是创建类的工厂,所有类的元类都是type类,所有类都是type类的实例对象。
如果自定义一个元类,在元类中重写__call__方法,判断当前是否有实例,没有实例才创建,有则不创建。对需要实现单例的类,指定类的元类是我们自定义的元类,从而实现单例。(不推荐使用此方法)
三、单例模式应用场景
单例模式适用于需要确保一个类只有一个实例对象,并且该对象需要被全局访问的情况。
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件、应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
实例--单例模式控制数据库连接池对象:
假设我们正在开发一个多线程的应用程序,其中包含一个数据库连接池对象。为了避免在多个地方重复创建数据库连接池对象,我们可以使用单例模式来确保该对象只会被创建一次,并且在多个线程之间共享一个对象。
python
import threading
class DatabaseConnectionPool:
instance = None
lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if cls.instance is None:
with cls.lock:
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
self.connections = []
def add_connection(self, connection):
self.connections.append(connection)
def get_connection(self):
return self.connections
# 使用示例
pool = DatabaseConnectionPool()
pool.add_connection("connection1")
pool.add_connection("connection2")
def worker(i):
pool = DatabaseConnectionPool() # 多个线程共享同一个对象
pool.add_connection("connection"+str(i+1))
connection = pool.get_connection()
print(f'Thread-{threading.get_ident()} got connection:{connection}')
if __name__ == '__main__':
for i in range(4):
t = threading.Thread(target=worker(i))
t.start()
上述代码中,我们首先定义了一个名为 DatabaseConnectionPool 的单例类,它维护了一个连接池列表 connections,通过 add_connection 和 get_connection 方法来添加和获取连接。使用 new 方法来创建单例对象,确保在多个线程之间只有一个实例,同时使用锁来保证线程安全。
然后,我们在多个线程中使用同一个连接池对象,并通过 get_connection 方法来获取连接。由于所有的线程都共享同一个连接池对象,因此在获取连接时不会出现资源浪费和重复创建对象等问题。