欢迎访问宙启技术站
智能推送

Python函数:如何使用装饰器实现函数的扩展或修饰

发布时间:2023-06-13 13:29:41

在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,在函数执行过程中出现了异常,异常信息被成功捕获和记录。