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

Python中的装饰器函数应用讲解

发布时间:2023-06-16 15:13:32

Python中的装饰器是一种可以在函数定义时动态修改函数行为的语法糖。它可以让我们在不改变函数原有代码的情况下,增加或修改某些函数的功能。装饰器本质上是一个函数,它接受一个函数作为参数,返回一个经过包装后的函数,该函数在被调用前,会先调用装饰器返回的新函数。

装饰器的语法很简单,它以@符号开始,后面跟着一个函数名。如果装饰器接受参数,那么它需要一个内层函数来接受参数,内层函数再返回真正的装饰器。

下面是一个装饰器的示例:

def func_decorator(func):
    def wrapper(*args, **kwargs):
        print("before function call")
        result = func(*args, **kwargs)
        print("after function call")
        return result
    return wrapper

@func_decorator
def say_hello(name):
    print("Hello, " + name)

say_hello("Tom")

在上面的示例中,我们定义了一个装饰器函数func_decorator,它接受一个函数作为参数,并返回一个内层函数wrapper。在wrapper函数中,我们添加了before function callafter function call的打印语句,来模拟装饰器的额外功能。最后,我们将say_hello函数用@func_decorator装饰起来,从而变成了一个新的函数。

say_hello("Tom")被调用时,Python会自动调用func_decorator,并将say_hello作为参数传入。func_decorator会返回一个包装后的新函数wrapper,该函数接受任意参数和关键字参数,并在调用原始函数之前和之后打印额外的信息。最后,装饰器返回wrapper函数,并且say_hello现在指向这个新函数。

运行上面的代码,输出如下:

before function call
Hello, Tom
after function call

我们可以看到,在调用say_hello函数时,装饰器会先打印before function call,然后调用原始的say_hello函数,再打印after function call

以上是装饰器的基本使用方法,下面我们将介绍一些常见的装饰器函数的应用。

## 记录函数执行时间

装饰器可以用来记录函数执行的时间,这对于调试和性能优化非常有用。我们可以定义一个装饰器函数timeit,在函数执行前打印当前时间,函数执行后打印执行时间。

import time

def timeit(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print("Elapsed time: {:.5f} seconds".format(end_time - start_time))
        return result
    return wrapper

@timeit
def slow_func(n):
    time.sleep(n)
    return n

print(slow_func(1))
print(slow_func(2))
print(slow_func(3))

在上面的代码中,我们定义了一个timeit装饰器函数,它接受一个函数作为参数,返回一个包装后的新函数。在新函数中,我们记录了函数执行前的时间戳start_time,并调用原始函数。在函数执行后,我们计算了执行时间end_time - start_time,并打印出来。

对于测试函数slow_func,我们在函数执行前调用了装饰器@timeit,从而将函数传递给timeit函数,返回一个新的函数。我们多次调用slow_func,并打印出结果和执行时间。运行上面的代码,输出如下:

1
Elapsed time: 1.00093 seconds
2
Elapsed time: 2.00009 seconds
3
Elapsed time: 3.00052 seconds

我们可以看到,在每次调用slow_func时,装饰器会记录函数调用前和调用后的时间,并打印出函数执行的时间。

## 缓存函数计算结果

装饰器还可以用来缓存函数的计算结果,避免重复计算,提高函数调用效率。我们可以定义一个装饰器函数cached,用来缓存函数调用的结果。在新函数中,我们首先查询缓存,如果有缓存结果则直接返回,否则调用原始函数,将结果缓存起来,并返回结果。

def cached(func):
    cache = {}
    def wrapper(*args, **kwargs):
        key = (args, tuple(sorted(kwargs.items())))
        if key in cache:
            print("Cache hit for", func.__name__)
            return cache[key]
        result = func(*args, **kwargs)
        cache[key] = result
        print("Cache miss for", func.__name__)
        return result
    return wrapper

@cached
def fib(n):
    if n < 2:
        return n
    else:
        return fib(n-1) + fib(n-2)

print(fib(20))
print(fib(21))
print(fib(22))
print(fib(23))

在上面的代码中,我们定义了一个cached装饰器函数,它接受一个函数作为参数,并返回一个新函数wrapper。在函数内部,我们定义了一个cache字典,用来存储对应函数的计算结果。在新函数中,我们将argskwargs组成一个元组作为字典的键,这样即使参数顺序不同,也可以缓存结果。在查询缓存时,我们判断当前参数是否被缓存过,如果有则直接返回结果,否则调用原始函数,并将结果缓存起来。

对于测试函数fib,我们在函数执行前调用了装饰器@cached,从而将函数传递给cached函数,返回一个新的函数。我们多次调用fib,并打印出结果和缓存命中情况。我们可以看到,对于较小的参数,装饰器的效果并不明显,但对于较大的参数,由于使用了缓存,函数调用的效率大大提高。

## 检查输入参数

装饰器还可以用来检查函数的输入参数是否符合要求,比如类型是否正确、是否缺少必要参数等等。我们可以定义一个装饰器函数check_input,在新函数中检查参数类型,如果有错误则抛出异常。

def check_input(func):
    def wrapper(*args, **kwargs):
        for arg in args:
            if not isinstance(arg, int):
                raise TypeError("Function argument must be an integer")
        if kwargs:
            raise ValueError("Function does not support keyword arguments")
        return func(*args, **kwargs)
    return wrapper

@check_input
def add(a, b):
    return a + b

print(add(1, 2))
print(add("1", "2"))

在上面的代码中,我们定义了一个check_input装饰器函数,它接受一个函数作为参数,并返回一个新函数wrapper。在新函数中,我们遍历了所有的位置参数,如果发现类型不是整数,则抛出异常。在检查完所有参数后,我们判断是否有关键字参数,如果有则抛出异常。最后调用原始函数,返回结果。

对于测试函数add,我们在函数执行前调用了装饰器@check_input,从而将函数传递给check_input函数,返回一个新的函数。我们多次调用add,并打印出结果和错误信息。在第一次调用时,由于参数类型正确,没有错误。在第二次调用时,由于第一个参数是字符串而不是