1. decorator装饰器是什么
在 Python 中,decorator 装饰器是一种包装函数的机制。我们可以用装饰器来包装一个已有的函数,通过添加一些额外的功能,使得这个函数变得更有用。装饰器本身是一个函数,这个函数接收一个函数作为参数,并且返回一个新的函数。Python 中的装饰器语法很简单,它使用 @ 符号来使用装饰器。
1.1 装饰器的基本用法
下面是一个非常简单的装饰器的例子,这个装饰器接收一个函数作为参数,并且返回一个新的函数,新的函数会在原函数执行前后输出一些日志信息。
def log(func):
def wrapper(*args, **kwargs):
print("Calling function: ", func.__name__)
result = func(*args, **kwargs)
print("Function returned: ", result)
return result
return wrapper
在上面的代码中,我们定义了一个名为 log 的装饰器。这个装饰器实际上是一个函数,这个函数接收一个函数作为参数,并且返回一个新的函数。这个新的函数就是我们定义的叫做 wrapper 的函数。
在 wrapper 函数中,我们首先打印了一条日志信息,这条日志信息是在原函数被调用前输出的。然后我们通过调用原函数来获取执行结果,执行结果赋值给了 result 变量。最后,我们又打印了一条日志信息,这条日志信息是在原函数被调用后输出的。最后,wrapper 函数返回了原函数的执行结果。
我们可以通过下面的代码来使用这个装饰器,这个代码展示了装饰器的基本用法。
@log
def foo(x, y):
return x + y
result = foo(1, 2)
在上面的代码中,我们使用了 @ 符号来应用装饰器。在这个例子中,我们定义了一个名为 foo 的函数,并且使用了 @log 装饰器将它装饰起来。因此,foo 函数会变成 log(foo) 函数,也就是新的 wrapper 函数。当我们调用 foo 函数时,实际上是在调用它的 wrapper 函数。wrapper 函数会在原函数被调用前后输出日志信息,然后调用原函数并返回原函数的执行结果。
1.2 装饰器的返回值
虽然装饰器通常返回一个新的函数,但是也可以返回其他类型的值。当装饰器返回一个与被装饰的函数不同的可调用对象时,实际上是将被装饰的函数变成了另一个函数。下面是一个例子。
def mydecorator(func):
print("mydecorator is called")
return 42
@mydecorator
def myfunction():
print("myfunction is called")
myfunction()
在上面的例子中,我们定义了一个名为 mydecorator 的装饰器,这个装饰器直接返回了数字 42。然后,我们使用了 @mydecorator 装饰了一个函数 myfunction,这个函数没做什么事情,只是打印了一条日志信息。接着,我们调用了 myfunction 函数。
运行上面的代码,会输出以下内容:
mydecorator is called
42
从输出可以看到,当我们使用 @mydecorator 装饰 myfunction 函数时,mydecorator 函数会立即被调用,并且返回了数字 42。这个数字会直接打印在屏幕上,说明装饰器的返回值代替了 myfunction 函数本身。因此,当我们调用 myfunction 函数时,实际上是在调用数字 42,而不是原来的 myfunction 函数。
2. 装饰器的应用场景
2.1 对函数进行计时
装饰器最常用的应用场景是为函数计时和记录日志。下面是一个简单的装饰器,它可以用来记录函数执行的时间。代码中使用了 time 模块中的 perf_counter 函数,这个函数返回当前系统的高精度计时器的值,单位是秒。
import time
def timethis(func):
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print("Function {} took {:.6f} seconds to run.".format(func.__name__, run_time))
return result
return wrapper
@timethis
def compute(n):
return sum(i**2 for i in range(n))
compute(10000)
在上面的代码中,我们定义了一个名为 timethis 的装饰器。这个装饰器可以用来计算函数执行的时间,并且输出执行时间。如果我们想要计算其他函数的执行时间,只需要在这个函数上方使用 @timethis 装饰器即可,它会自动计算函数的执行时间并输出。
2.2 包裹函数
有些函数需要处理一些额外的逻辑,比如说输入输出的检查或者异常处理等等。这些逻辑并不是业务逻辑的一部分,而只是为了提高代码的稳定性和可读性。这时候我们可以使用装饰器来包裹这些逻辑,让业务逻辑更加简洁明了。
下面是一个例子,演示了如何用装饰器来检查函数的输入和输出。
def check_input_output(func):
def wrapper(*args, **kwargs):
# check input
for arg in args:
if not isinstance(arg, (int, float)):
raise TypeError("Invalid input type")
# execute function
result = func(*args, **kwargs)
# check output
if not isinstance(result, (int, float)):
raise TypeError("Invalid output type")
return result
return wrapper
@check_input_output
def multiply(x, y):
return x * y
result = multiply(2, 3.5)
print(result)
在上面的例子中,我们定义了一个名为 check_input_output 的装饰器。这个装饰器可以用来检查函数的输入和输出,确保输入和输出的类型符合要求。在 wrapper 函数中,我们首先检查了输入参数,如果发现输入参数不是整数或浮点数类型,就会抛出一个异常。接着我们执行了原函数,获取了执行结果。最后,我们检查了输出结果,如果输出结果不是整数或浮点数类型,就会抛出一个异常。最后,我们返回了原函数的执行结果。
在上面的例子中,我们使用了 @check_input_output 装饰器来装饰 multiply 函数。当我们调用 multiply 函数时,装饰器会自动检查输入和输出类型是否合法。
2.3 缓存函数结果
有些函数执行时间比较长,并且返回结果不会随着输入参数的改变而改变。此时,我们可以使用缓存来优化性能,缓存已经计算过的结果,在下一次计算时直接返回缓存结果。下面是一个例子,演示了如何用装饰器来缓存函数的结果。
def cache(func):
cache_dict = {}
def wrapper(*args):
if args in cache_dict:
return cache_dict[args]
result = func(*args)
cache_dict[args] = result
return result
return wrapper
@cache
def fibonacci(n):
if n in (0, 1):
return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(100)
在上面的例子中,我们定义了一个名为 cache 的装饰器,这个装饰器可以用来缓存函数的结果。在 wrapper 函数中,我们使用了一个字典来缓存已经计算过的结果。如果函数的参数已经在字典中存在过了,那么就直接返回缓存结果。如果函数的参数没有在字典中存在过,那么就调用原函数来计算结果,并把结果保存到字典中。最后,wrapper 函数返回了结果。
当我们使用 @cache 装饰器来装饰 fibonacci 函数时,就会自动启用缓存机制。当我们计算 fibonacci(100) 函数时,实际上是在调用 fibonacci 函数的 wrapper 版本,wrapper 版本会缓存已经计算过的结果,以后再次计算相同参数的结果时,就可以直接返回缓存结果,从而节省计算时间。
3. decorator装饰器的使用技巧
3.1 装饰器的堆叠
在 Python 中,可以用 @ 符号来连续堆叠多个装饰器,这样可以使得函数的实现更加灵活。下面是一个例子,演示了如何使用装饰器的堆叠功能。
def log(func):
def wrapper(*args, **kwargs):
print("Calling function: ", func.__name__)
result = func(*args, **kwargs)
print("Function returned: ", result)
return result
return wrapper
def cache(func):
cache_dict = {}
def wrapper(*args):
if args in cache_dict:
return cache_dict[args]
result = func(*args)
cache_dict[args] = result
return result
return wrapper
@log
@cache
def fibonacci(n):
if n in (0, 1):
return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(100)
在上面的例子中,我们定义了两个装饰器,分别是 log 和 cache。log 装饰器用来记录函数的调用信息,cache 装饰器用来缓存函数的结果。当我们需要同时使用这两个装饰器时,可以使用 @ 符号来连接它们。
在上面的例子中,我们使用了 @log 和 @cache 装饰器,这两个装饰器的顺序是从下向上的。也就是说,先应用了 cache 装饰器,再应用了 log 装饰器。当我们调用 fibonacci 函数时,实际上是在调用它的 wrapper 函数。这个 wrapper 函数会先使用 cache 装饰器来缓存结果,再使用 log 装饰器来记录调用信息。最后,wrapper 函数返回了原函数的执行结果。
3.2 可接受参数的装饰器
如果要编写一个能够接受参数的装饰器,可以再套一个函数,这个函数的参数就是装饰器所需的参数。下面是一个例子,演示了如何编写可接受参数的装饰器。
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def hello():
print("Hello, world!")
hello()
在上面的代码中,我们定义了一个名为 repeat 的函数,这个函数接收一个参数 n。然后 repeat 函数返回一个 decorator 函数,这个函数接收一个 func 函数作为参数。在 decorator 函数中,我们定义了一个 wrapper 函数,它会调用 func 函数 n 次。最后,decorator 函数返回了 wrapper 函数。
当我们使用 @repeat(3) 装饰 hello 函数时,实际上是在调用 repeat(3) 函数,这个函数返回了 decorator 函数,decorator 函数返回了 wrapper 函数。因此,在调用 hello 函数时,实际上是在调用它的 wrapper 版本,wrapper 版本会把 hello 函数执行了三次。最后,wrapper 函数返回最后一次执行结果,也就是字符串 "Hello, world!"。
4. 总结
decorator 装饰器是 Python 中的一个重要功能,它可以包装已有函数,通过添加一些额外的功能,使得这个函数变得更有用。Python 中的装饰器语法很简单,它使用 @ 符号来使用装饰器。装饰器最常用的应用场景是为函数计时和记录日志,同时也可以用来包裹函数,缓存函数结果等等。为了提高灵活性,我们可以使用装饰器的堆叠功能,也可以编写可接受参数的装饰器。掌握 decorator 装饰器的使用技巧,是 Python 程序员必不可少的技能之一。