什么是包装器
包装器是一种可用于“装饰”(即增强)函数或类的 Python 设计模式。具体而言,它是一种可用于修改现有函数或类行为的函数或类。例如,可以使用包装器实现日志记录、缓存、输入验证等,同时又不必修改原始函数或类的源代码。
Python 中的函数装饰器(decorator)实际上是一种特殊的包装器,其范围仅限于函数。虽然装饰器可以通过包装器实现,但是由于其应用广泛且语法简洁,因此 Python 专门提供了装饰器语法。
如何在函数上添加包装器
Python 中添加包装器的方法包括使用函数装饰器和类装饰器两种方式。下面分别介绍。
使用函数装饰器
使用函数装饰器,需要定义一个包装器函数,并将待装饰函数作为其参数。例如,下面的例子定义了一个计时器装饰器,用于记录函数执行的时间:
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} 执行时间:{end_time - start_time:.5f}")
return result
return wrapper
@timer
def my_func():
time.sleep(1)
my_func()
运行结果如下:
my_func 执行时间:1.00037
解释一下上面的代码,首先定义了一个计时器装饰器 timer
,它的作用是在函数执行前后记录时间。紧接着,定义了一个函数 my_func
并在其上面应用了计时器装饰器 @timer
,这样当执行 my_func
时,计时器装饰器就会开始记录时间,并在函数执行完毕后打印出记录结果。
注:在包装器函数 wrapper
内部,可以使用 *args
和 **kwargs
接收任意数量的位置参数和关键字参数,并在调用原始函数时传递给它们。
使用类装饰器
使用类装饰器,需要定义一个装饰器类,并在其 __call__
方法中实现包装器逻辑。例如,下面的例子定义了一个缓存装饰器类,用于缓存指定函数的结果:
class cache:
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if args in self.cache:
print('从缓存中读取结果')
return self.cache[args]
else:
result = self.func(*args)
self.cache[args] = result
return result
@cache
def my_func(n):
print(f"正在计算 {n} 的阶乘...")
# 计算 n 的阶乘
res = 1
for i in range(2, n+1):
res *= i
return res
print(my_func(5))
print(my_func(5))
运行结果如下:
正在计算 5 的阶乘...
120
从缓存中读取结果
120
解释一下上面的代码,首先定义了一个缓存装饰器类 cache
,它的作用是缓存带有单个参数的函数的结果,并在第二次调用时直接从缓存中读取结果。紧接着,在定义了一个函数 my_func
并在其上面应用了缓存装饰器 @cache
,这样当第一次执行 my_func(5)
时,缓存装饰器会将计算结果缓存下来;而当第二次执行 my_func(5)
时,缓存装饰器会直接从缓存中读取结果并返回,而不需要再次计算。
注:在装饰器类中,可以在 __init__
方法中初始化装饰器参数,并在 __call__
方法中实现包装器逻辑。
温度转换例子
下面我们以一个温度转换例子来说明如何应用包装器。假设我们有一个名为 celsius_to_fahrenheit
的函数,用于将摄氏温度转换为华氏温度。我们希望在该函数前后记录时间,并对其输入做验证,确保输入的温度值为实数。下面是相应的实现代码:
import time
def timer(func):
"""
计时器装饰器
"""
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} 执行时间:{end_time - start_time:.5f}")
return result
return wrapper
def validate_input(func):
"""
输入验证装饰器
"""
def wrapper(*args, **kwargs):
for arg in args:
if not isinstance(arg, (int, float)):
raise ValueError("输入的温度值必须为实数")
return func(*args, **kwargs)
return wrapper
@timer
@validate_input
def celsius_to_fahrenheit(deg_celsius):
"""
将摄氏温度转换为华氏温度
"""
deg_fahrenheit = (9/5) * deg_celsius + 32
time.sleep(1)
return deg_fahrenheit
print(celsius_to_fahrenheit(0.0))
运行结果如下:
celsius_to_fahrenheit 执行时间:1.00101
32.0
解释一下上面的代码,首先定义了一个计时器装饰器 timer
和一个输入验证装饰器validate_input
。计时器装饰器用于记录函数执行时间,而验证输入装饰器用于确保输入的温度值为实数。在 celsius_to_fahrenheit
函数定义上方,分别使用了这两个装饰器,其中 @timer
表示时间记录装饰器应用在了 @validate_input
表示输入验证装饰器之上。
当然,也可以在定义函数时直接将装饰器“叠”在一起,如下所示:
@timer
@validate_input
def celsius_to_fahrenheit(deg_celsius):
pass
注:在使用多个装饰器时,装饰器会按照从上到下的顺序逐层包装函数。例如,上面的代码会先应用输入验证装饰器,然后应用计时器装饰器。
总结
Python 中的包装器是一种可用于“装饰”函数或类的设计模式,它可以在不修改原始函数或类的源代码的情况下增强其行为。常见的装饰器场景包括日志记录、缓存、输入验证等。在 Python 中,可以使用函数装饰器和类装饰器两种方式来实现包装器。使用函数装饰器时,只需定义一个包装器函数,并在待装饰函数上面应用装饰器即可;使用类装饰器时,需要定义一个装饰器类,并在其 __call__
方法中实现包装器逻辑。
以上是一个温度转换例子,我们演示了如何添加装饰器在函数中添加包装器。在 Python 中添加包装器的方法非常灵活,这为开发人员提供了很大的发挥空间,让我们能够更好的优化和掌握 Python 的编程技术。