Python语法详解之decorator装饰器

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 程序员必不可少的技能之一。

后端开发标签