Python 在函数上添加包装器

什么是包装器

包装器是一种可用于“装饰”(即增强)函数或类的 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 的编程技术。

后端开发标签