Python中的装饰器函数应用讲解
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 call和after 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字典,用来存储对应函数的计算结果。在新函数中,我们将args和kwargs组成一个元组作为字典的键,这样即使参数顺序不同,也可以缓存结果。在查询缓存时,我们判断当前参数是否被缓存过,如果有则直接返回结果,否则调用原始函数,并将结果缓存起来。
对于测试函数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,并打印出结果和错误信息。在第一次调用时,由于参数类型正确,没有错误。在第二次调用时,由于第一个参数是字符串而不是
