Python函数:如何使用装饰器实现函数的扩展或修饰
在Python中,装饰器是一种特殊的函数,它可以接受一个函数作为参数,并返回一个新的函数,使用装饰器可以实现函数的扩展或修饰,让代码更加简洁、易读、易维护。
装饰器的基本语法是:
def decorator(func): # 定义装饰器函数,接受一个函数参数
def wrapper(*args, **kwargs): # 定义包装函数,用来扩展或修饰原函数
# 在原函数前后执行一些操作
return func(*args, **kwargs) # 调用原函数并返回其结果
return wrapper # 返回包装函数
通过上面的语法,我们就可以定义一个装饰器函数,然后使用@符号将其应用于目标函数,如下所示:
@decorator # 使用装饰器
def my_func(x, y): # 目标函数
return x + y
以上代码等同于:
my_func = decorator(my_func)
下面,我们来看看如何使用装饰器实现函数的扩展或修饰。
1. 记录函数执行时间
我们希望记录函数的执行时间,以便于性能优化或调试,可以定义一个计时器装饰器,如下所示:
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__} executed in {end_time - start_time} seconds.") # 打印函数执行时间
return result # 返回原函数结果
return wrapper
上述代码中,我们先定义了一个计时器装饰器,然后将其应用于目标函数,调用目标函数时将会自动记录函数的执行时间,以秒为单位。
@timer
def my_func(x, y):
time.sleep(1) # 人为延迟1秒
return x + y
my_func(1, 2)
输出结果为:
my_func executed in 1.0021977424621582 seconds.
2. 缓存函数执行结果
有些函数的计算复杂度很高,如果多次调用同一函数,可能会造成性能瓶颈,此时,我们可以使用缓存装饰器对结果进行缓存,避免重复计算,如下所示:
def cache(func):
memo = {} # 缓存结果的字典
def wrapper(*args, **kwargs):
key = (args, tuple(kwargs.items())) # 计算缓存key
if key not in memo:
memo[key] = func(*args, **kwargs) # 如果缓存中没有结果,则调用原函数进行计算
return memo[key] # 返回缓存结果
return wrapper
上述代码中,我们定义了一个缓存装饰器,将其应用于目标函数时,会自动对目标函数的执行结果进行缓存。缓存key的计算方式为将函数调用的参数(也就是args和kwargs)组成一个元组,再将kwargs转换成元组格式。
@cache
def my_func(x, y):
time.sleep(1) # 人为延迟1秒
return x + y
print(my_func(1, 2)) # 调用my_func函数
print(my_func(1, 2)) # 再次调用my_func函数,从缓存中获取结果
输出结果为:
3 3
可以看到,第二次调用my_func函数时,直接从缓存中获取结果,省去了函数的计算过程。
3. 验证函数参数类型
在开发过程中,我们经常会遇到函数参数类型不匹配的问题,为了避免这个问题,我们可以定义一个参数类型验证装饰器,如下所示:
def validate(types):
def decorate(func):
assert len(types) == func.__code__.co_argcount, f"{func.__name__} requires {func.__code__.co_argcount} arguments." # 参数数量校验
def wrapper(*args):
for i, arg in enumerate(args):
assert isinstance(arg, types[i]), f"{i}-th argument is not {types[i]} type." # 参数类型校验
return func(*args) # 调用原函数
return wrapper
return decorate
上述代码中,我们定义了一个参数类型验证装饰器,该装饰器接受一个参数types,用于指定参数类型,如果参数不满足类型要求,会抛出异常。在wrapper函数中,我们首先校验参数的数量是否满足要求,然后根据参数的位置依次校验参数类型,最后调用原函数。
@validate([int, int])
def my_func(x, y):
return x + y
print(my_func(1, 2)) # 调用my_func函数
print(my_func("1", 2)) # 抛出异常,因为 个参数不是int类型
输出结果为:
3 AssertionError: 0-th argument is not <class 'int'> type.
可以看到,第二次调用my_func函数时,由于 个参数类型不满足要求,抛出了异常。
4. 捕获函数异常
在函数执行过程中,可能会出现异常情况,为了更好的处理异常,我们可以定义一个异常捕获装饰器,如下所示:
def catch(func):
def wrapper(*args, **kwargs):
try:
result = func(*args, **kwargs) # 调用原函数
except Exception as ex:
print(f"Exception {ex} caught in {func.__name__}.") # 记录异常信息
return None # 返回None代表函数执行失败
return result # 返回原函数结果
return wrapper
上述代码中,我们定义了一个异常捕获装饰器,在wrapper函数中,我们使用try-except语句捕获原函数执行过程中可能出现的异常,并记录异常信息。如果函数执行成功,则返回函数结果;否则,返回None代表函数执行失败。
@catch
def my_func(x, y):
return x / y
print(my_func(1, 0)) # 调用my_func函数
输出结果为:
Exception division by zero caught in my_func. None
可以看到,由于第二个参数为0,在函数执行过程中出现了异常,异常信息被成功捕获和记录。
