[学习视频]
P7单例模式
场景:程序执行期间某一个类要求全程只需一个实例对象。
1、使用__new__方法实现
(1) 对重写__new__方法的类本身实现单例(原理:__new__创建类本身实例,返回值为所创建的类本身实例)
此方法适合程序执行期间只有某一个类要求全程只需一个实例对象。
class Add(object):
__singleton = None
def __new__(cls, *args, **kwargs):
if not cls.__singleton:
cls.__singleton = super(Add, cls).__new__(cls)
return cls.__singleton
def __init__(self, *args, **kwargs):
if not self.__singleton:
pass # 初始化操作
def add(self, a, b):
return a + b
if __name__ == '__main__':
add = Add()
print(id(add))
add1 = Add()
print(id(add1))
print("两个数的和为:", add.add(1, 2))
"""
运行结果:
1630842487920
1630842487920
两个数的和为: 3
Process finished with exit code 0
"""
此方法适合程序执行期间只有某一个类要求全程只需一个实例对象。
(2) 重写__new__方法,使其返回值为需要实现单例的某个类(原理:__new__创建其它类实例,返回值为所创建的其它类实例)
【注意】:__new__返回值为其它类实例时,__init__方法不会执行
class Singleton:
def __new__(cls, singleton, *args, **kwargs):
if not hasattr(cls, "instance"):
cls.instance = singleton(*args, **kwargs)
return cls.instance
def __init__(self): # 由于__new__返回的不是Singleton实例因此不会调用该__init__方法
print("Base.__init__")
class Add():
def __init__(self, a, b):
self.a = a
self.b = b
def add(self):
return self.a + self.b
if __name__ == '__main__':
add = Singleton(Add,1, 2)
print(add.a, add.b)
add1 = Singleton(Add, 2, 3)
print(add.a, add.b, add1.a, add1.b)
print(add is add1)
print("两个数的和为:", add.add())
"""
运行结果:
1 2
1 2 1 2
True
两个数的和为: 3
Process finished with exit code 0
"""
2、使用装饰器实现
使用装饰器的方法适合程序执行期间有多个类被要求全程只需一个实例对象。
(1) 使用装饰函数(原理:闭包函数)
from functools import wraps
def singleton(cls):
"""
功能:用于保证程序运行过程中某个类的实例始终只有一个
"""
obj = None
@wraps(cls)
def func_in(*args, **kwargs): # *args, **kwargs ,创建Add对象时用于接收Add()的参数,此处没有参数
nonlocal obj
if not obj:
obj = cls(*args, **kwargs)
return obj
return func_in
@singleton
class Add(object):
def __init__(self, a, b):
self.a = a
self.b = b
def add(self):
return self.a + self.b
if __name__ == '__main__':
add = Add(1, 2)
print(add.a, add.b)
add1 = Add(2, 3)
print(add.a, add.b, add1.a, add1.b)
print(add is add1)
print("两个数的和为:", add.add())
"""
运行结果:
1 2
1 2 1 2
True
两个数的和为: 3
Process finished with exit code 0
"""
(2)使用装饰类(原理:重新定义__init__()、__call__()方法的类作为装饰器)
class Singleton(object):
"""
功能:用于保证程序运行过程中某个类的实例始终只有一个
"""
__singleton = None
def __init__(self, cls):
if not self.__singleton:
self.cls = cls
def __call__(self, *args, **kwargs): # *args, **kwargs ,创建Add对象时用于接收Add()的参数,此处没有参数
if not self.__singleton:
self.__singleton = self.cls(*args, **kwargs)
return self.__singleton
@Singleton
class Add(object):
def __init__(self, a, b):
self.a = a
self.b = b
def add(self):
return self.a + self.b
if __name__ == '__main__':
add = Add(1, 2)
print(add.a, add.b)
add1 = Add(2, 3)
print(add.a, add.b, add1.a, add1.b)
print(add is add1)
print("两个数的和为:", add.add())
"""
运行结果:
1 2
1 2 1 2
True
两个数的和为: 3
Process finished with exit code 0
"""
3、使用metaclass
metaclass可以实现对一个可调用对象的调用
(1) 使用自定义可调用元类实现
自定义元类详解
原理:
- (i) 自定义元类__new__方法实现创建Add类对象
- (ii) 自定义元类__init__方法实现初始化Add类对象
- (iii) 自定义元类__call__方法实现实例化Add类对象,即运行Add()时会运行自定义元类中重写的__call__方法,并在此过程中会调用Add类对象中重写的__new__和__init__,进行创建Add类对象的实例对象和初始化该实例对象。
class Singleton(type):
"""
功能:用于保证程序运行过程中某个类的实例始终只有一个
"""
__singleton = None
# 此处的 __new__和__init__只是为了表明自定义元类中这两个方法的意义,并没有使用到
# def __new__(cls, class_name, base_names_typle, attribute_dict):
# super().__new__(cls, class_name, base_names_typle, attribute_dict)
#
# def __init__(self, class_name, base_names_typle, attribute_dict):
# super().__init__(class_name, base_names_typle, attribute_dict)
def __call__(self, *args, **kwargs):
if not self.__singleton:
self.__singleton = super().__call__(*args, **kwargs)
return self.__singleton
"""
metaclass=Singleton 解析:
在解释器执行到class Add(object, metaclass=Singleton)时运行过程如下
下面的csl_、bases_、attritube_解释器会自动传递
相当于 Add = Singleton(cls_, bases_, attritube_), Add指向转变为Singleton的实例对象。这也解释了:
(1)为什么加上该语句后Add的属性会改变为Singleton的属性,如:print(Add.__class__)为Singleton;
(2)为什么Singleton的__new__和__init__只会执行一次;
(3)后期调用Add实际上调用该Singleton实例所绑定的__call__方法。
此过程与装饰函数为装饰某个类的过程类似
不同之处在于对于外函数解释器自动传递的参数不同,装饰函数为装饰某个类时解释器只为其传递所要装饰的类的cls。
"""
class Add(object, metaclass=Singleton):
def __init__(self, a, b):
self.a = a
self.b = b
def add(self):
return self.a + self.b
if __name__ == '__main__':
add = Add(1, 2)
print(add.a, add.b)
add1 = Add(2, 3)
print(add.a, add.b, add1.a, add1.b)
print(add is add1)
print("两个数的和为:", add.add())
"""
运行结果:
1 2
1 2 1 2
True
两个数的和为: 3
Process finished with exit code 0
"""
(2) 使用闭包函数结合动态创建类实现
原理:
(i) 闭包函数可以使用外部函数变量维持内部函数状态;
(ii) 在外部函数需要使用type()创建一个需要实现单例类的类,即:类Add;
(iii)在内部函数实现单例类的实例化,即:Add的实例化。
from functools import wraps
def singleton(cls, bases, attritube):
cls = type(cls, bases, attritube)
obj = None
@wraps(cls)
def function_in(*args, **kwargs):
nonlocal obj
if not obj:
obj = cls(*args, **kwargs)
return obj
return function_in
"""
metaclass=singleton 解析:
下面的csl_、bases_、attritube_解释器会自动传递
相当于 Add = singleton(cls_, bases_, attritube_), Add指向转变为闭包的内部函数。这也解释了:
(1)为什么加上该语句后Add的一些属性会改变为闭包的内部函数属性,如:Add.__name__、Add.__doc__,往往需要在闭包内部函数上加上@warp装饰器;
(2)为什么闭包外部函数只会执行一次;
(3)后期调用Add实际上调用的闭包内部函数
此过程与装饰函数为装饰某个类的过程类似
不同之处在于对于外函数解释器自动传递的参数不同,装饰函数为装饰某个类时解释器只为其传递所要装饰的类的cls。
"""
class Add(object, metaclass=singleton):
def __init__(self, a, b):
self.a = a
self.b = b
def add(self):
return self.a + self.b
if __name__ == '__main__':
add = Add(1, 2)
print(add.a, add.b)
add1 = Add(2, 3)
print(add.a, add.b, add1.a, add1.b)
print(add is add1)
print("两个数的和为:", add.add())
"""
运行结果:
1 2
1 2 1 2
True
两个数的和为: 3
Process finished with exit code 0
"""
4. 共享属性
class Singleton(object):
_state = {}
def __new__(cls, *args, **kw):
ob = super(Singleton, cls).__new__(cls, *args, **kw)
ob.__dict__ = cls._state
return ob
if __name__ == '__main__':
a = Singleton()
b = Singleton()
print(a is b)
b.abc = 10
print(a.abc)
"""
运行结果:
False
10
Process finished with exit code 0
"""
5. import方法
作为python的模块是天然的单例模式
# singleton.py
class Singleton(object):
def func(self):
pass
singleton = Singleton()
# test.py
from singleton import singleton
if __name__ == '__main__':
singleton.abc = 10
print(singleton.abc)
"""
运行结果:
10
Process finished with exit code 0
"""
6. 应用场景
-
Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
-
windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
-
网站的计数器,一般也是采用单例模式实现,否则难以同步。
-
应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
-
Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
-
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
-
多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
-
操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
总结以上,不难看出:
单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。