1. 函数装饰器的概念
在Python中,函数是一等对象,可以赋值给变量、作为参数传递、作为返回值。同时,Python还支持一种高级特性,即函数装饰器(Function Decorator)。
所谓函数装饰器,就是在不改变函数源代码和调用方式的前提下,给函数增加新的功能。它是通过一个用@符号修饰的Python函数进行声明的,接受一个函数作为输入,并返回另一个函数作为输出。
def decorator_function(original_function):
def wrapper_function():
print("Wrapper function executed this before the original function!")
return original_function()
return wrapper_function
@decorator_function
def display():
print("Display function ran")
display()
运行这段代码,我们可以得到以下输出:
Wrapper function executed this before the original function!
Display function ran
我们分别来看看这个代码段的三个组成部分:
1.1 decorator_function
这个函数是一个装饰器函数,它接受一个名为original_function的函数为输入,并返回一个内部函数wrapper_function。它的作用是将original_function函数进行装饰。
1.2 wrapper_function
这个函数是一个闭包(Closure),它可以访问decorator_function函数的局部变量,把原函数original_function包含在其中,并在调用原函数前后加上自己的逻辑。在本例中,wrapper_function在调用原函数前输出一条文字信息,然后返回原函数的执行结果。
1.3 @decorator_function
这是使用装饰器的语法糖。它相当于执行了以下代码:
display = decorator_function(display)
即把名为display的函数作为参数传入decorator_function函数,并将decorator_function函数的返回值重新赋值给display。此时,display指向的是wrapper_function函数。
2. 应用实例:计算函数运行时间
下面我们来看一个使用装饰器计算函数执行时间的例子。这个装饰器接受一个函数为输入,输出另一个函数,这个输出函数相当于原函数加上计算运行时间的功能。代码如下:
import time
def time_it(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Function {func.__name__} executed in {end_time - start_time:.12f}s")
return result
return wrapper
@time_it
def count_up_to(number):
total = 0
for i in range(1, number+1):
total += i
return total
print(count_up_to(1000000))
输出:
Function count_up_to executed in 0.026266813278s
500000500000
我们用time_it装饰了count_up_to函数,并执行它。我们看到输出了计算时间和函数的返回值。
这个例子中,我们在wrapper函数内部调用了func函数,并计算了它的执行时间。最后输出了这个执行时间,并返回了原来的函数结果。
3. 装饰器的有用技巧
3.1 验证函数参数
我们可以通过装饰器来验证函数的参数类型、数量和取值范围等。以下代码演示了验证函数参数是否为数字的方法。
def require_number(func):
def wrapper(*args, **kwargs):
for arg in args:
if type(arg) not in [int, float]:
raise ValueError("Argument must be a number!")
return func(*args, **kwargs)
return wrapper
@require_number
def multiply(num1, num2):
return num1 * num2
print(multiply(2, 3))
print(multiply("2", 3))
输出:
6
ValueError: Argument must be a number!
可以看到,我们先定义了一个装饰器函数require_number,它用于验证输入参数是否为数字。然后我们通过在multiply函数定义前添加@require_number,来对multiply函数进行装饰。
3.2 缓存函数结果
有些函数计算结果比较耗时。如果函数的输入相同,那么计算结果也相同。因此,我们可以使用装饰器来缓存函数的计算结果,避免重复计算。
def memoize(func):
cache = {}
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoize
def fibonacci(num):
if num in [0, 1]:
return num
else:
return fibonacci(num-1) + fibonacci(num-2)
print(fibonacci(40))
输出:
102334155
我们定义了一个装饰器函数memoize,它使用字典cache来缓存函数的计算结果。当函数被调用时,我们首先检查函数的输入参数args是否已经在cache中有结果,如果有结果直接返回,否则调用原函数进行计算,并将计算结果加入到cache中。
我们用这个装饰器来装饰了递归计算斐波那契数列的函数fibonacci。由于斐波那契数列会逐渐增加计算量,所以我们计算了第40项,需要很长时间。但是如果我们多次调用这个函数来计算其他项目,则可以显著减少计算时间。
3.3 记录函数执行日志
我们可以把函数执行的日志记录下来,以便后续查看。以下代码演示了使用装饰器记录函数执行日志的方法。
def logger(func):
import logging
logging.basicConfig(filename=f"{func.__name__}.log", level=logging.INFO)
def wrapper(*args, **kwargs):
logging.info(
f"Ran with args: {args}, and kwargs: {kwargs}")
return func(*args, **kwargs)
return wrapper
@logger
def my_function(arg1, arg2):
print(arg1 + arg2)
my_function(4, 5)
运行这个代码,我们可以看到在程序所在目录下生成了一个my_function.log文件,里面记录了函数的执行日志:
INFO:root:Ran with args: (4, 5), and kwargs: {}
我们定义了一个装饰器函数logger,它使用Python标准库中的logging模块记录日志,并将日志输出到文件中。
以上就是函数装饰器的一些常见用法和技巧。在实际代码编写中,我们可以通过使用装饰器来提高代码的可读性、可重用性以及安全性等方面的优化。