进一步介绍第二种方式,wrapper是如何感知到 类被实例化的?
进一步介绍第二种方式,wrapper是如何感知到 类被实例化的?
思考过程
在第二种方法中,包装函数如何感知类的实例化状态?
复习单例模式的装饰器实现。
今我在解释,使用'@singleton'注解定义的'SingletonClass',实际上会调用'wrapper'函数来实例化。这让我想到,'wrapper'函数监测类的实例化过程。
深入探讨装饰器应用
我正在讲解装饰器如何实现单例模式,特别是如何使用wrapper函数感知和控制类的实例化过程。通过维护一个字典来记录实例,确保只创建一个实例。
方法二:使用装饰器实现单例模式
好的,我来进一步详细介绍第二种使用装饰器的方式,解释wrapper
函数是如何感知到类被实例化的。
代码回顾
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class SingletonClass:
pass
原理详解
1. 装饰器的基本概念
- 装饰器:在Python中,装饰器是一个高阶函数,接受一个函数或类作为参数,并返回一个新的函数或类。它可以在不修改原始函数或类的情况下,扩展其功能。
2. 装饰器的执行过程
当我们使用
@singleton
装饰SingletonClass
时,实际上发生了以下步骤:定义类:首先,
SingletonClass
类被定义。应用装饰器:然后,
singleton
装饰器被调用,cls
参数接收到SingletonClass
类。返回包装器:装饰器返回内部定义的
wrapper
函数。替换原类:
SingletonClass
被替换为wrapper
函数,这意味着以后对SingletonClass
的调用实际上是对wrapper
函数的调用。
3. wrapper
函数如何感知实例化
替代实例化过程:由于
SingletonClass
已经被替换为wrapper
函数,当我们试图创建SingletonClass
的实例时,实际上是在调用wrapper
函数。参数捕获:
wrapper
函数接受任意数量的位置参数和关键字参数(*args, **kwargs
),这些参数原本是传递给类构造函数的。实例管理:
wrapper
函数内部维护了一个名为instances
的字典,用于存储类的实例。首次实例化:如果
cls
(即SingletonClass
)不在instances
中,表示还没有创建过实例,于是调用cls(*args, **kwargs)
创建一个新的实例,并将其存储在instances
中。后续实例化:如果
cls
已经在instances
中,直接返回已存在的实例。
4. 详细执行流程
代码执行顺序:
定义阶段:
- 定义
SingletonClass
。 - 应用装饰器,
SingletonClass
被替换为wrapper
函数。
- 定义
实例化阶段:
当调用
SingletonClass()
时,实际上是调用wrapper()
。wrapper
函数执行以下操作:检查
instances
字典中是否存在cls
的实例。如果不存在,创建新实例并存储。
如果存在,直接返回已有实例。
示意图:
SingletonClass --> wrapper (替换) SingletonClass() --> wrapper()
5. 示例代码与输出
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
print(f"Calling wrapper for {cls.__name__}")
if cls not in instances:
print(f"No instance exists for {cls.__name__}, creating one.")
instances[cls] = cls(*args, **kwargs)
else:
print(f"Instance already exists for {cls.__name__}, returning it.")
return instances[cls]
return wrapper
@singleton
class SingletonClass:
def __init__(self, value):
print(f"Initializing SingletonClass with value: {value}")
self.value = value
# 测试代码
s1 = SingletonClass(10)
s2 = SingletonClass(20)
print(f"s1.value: {s1.value}")
print(f"s2.value: {s2.value}")
print(f"s1 is s2: {s1 is s2}")
输出结果:
Calling wrapper for SingletonClass
No instance exists for SingletonClass, creating one.
Initializing SingletonClass with value: 10
Calling wrapper for SingletonClass
Instance already exists for SingletonClass, returning it.
s1.value: 10
s2.value: 10
s1 is s2: True
解释:
首次实例化
s1
:wrapper
被调用,检测到没有SingletonClass
的实例。创建新实例,调用
SingletonClass.__init__(10)
。
再次实例化
s2
:wrapper
被调用,检测到已有实例。返回已有实例,
__init__
不会再次执行。
结果验证:
s1
和s2
是同一个实例,s1 is s2
为True
。s2.value
仍然是10
,即首次初始化的值。
为什么wrapper
能够感知类的实例化
函数替代类:通过装饰器,
SingletonClass
被wrapper
函数替代。实例化调用被拦截:任何对
SingletonClass
的实例化调用SingletonClass(*args, **kwargs)
,都被重定向为对wrapper(*args, **kwargs)
的调用。控制权:
wrapper
函数获得了对实例化过程的完全控制,可以决定何时创建新实例,何时返回已有实例。
关键点总结
装饰器返回值:装饰器返回的
wrapper
函数取代了原始类,使得对类的调用实际上是对函数的调用。闭包特性:
wrapper
函数是一个闭包,持有对instances
字典和cls
的引用。实例存储:
instances
字典存储了类的实例,实现了对实例的全局管理。多类支持:如果使用同一个
singleton
装饰器装饰多个类,instances
字典会分别管理它们的实例。
注意事项
参数传递:
wrapper
需要接受*args
和**kwargs
,以适应不同的构造函数参数。线程安全:在多线程环境下,需要考虑对
instances
字典的访问同步,可能需要使用线程锁。不可继承性:这种方式对继承关系的处理需要谨慎,可能需要额外的逻辑。
总结
wrapper
感知实例化的机制:因为wrapper
函数替代了原始的类,所有对类的实例化请求都由wrapper
函数处理。实现单例的关键:通过在
wrapper
中控制实例的创建和返回,实现了单例模式。优势:这种方法简单直观,易于理解和实现,适用于不需要复杂继承关系的场景。
希望通过以上详细解释,你能清楚地了解第二种方式中wrapper
函数是如何感知并控制类的实例化过程的。