Python 进阶教程系列 2:装饰器¶
本文是 Python 进阶教程系列 2,主要介绍了装饰器的机制和用法。
Python 是一种功能强大的编程语言,其灵活性和可扩展性使得开发者能够创造出各种强大且高效的应用程序。其中一个让 Python 如此受欢迎的特性就是装饰器(Decorators)。
装饰器是一种可以动态地修改某个类或函数的行为的函数,它们在不修改源代码的情况下为已经存在的函数或类添加额外的功能。
我对装饰器的理解是:装饰器即为“传入一个函数,传出一个被加工后的函数”的函数。
装饰器的概念和用法¶
假设我们有一个函数,用于计算两个数字的和:
现在假设我们的需求是在函数执行前后打印出一些额外的信息,如参数和结果。我们可以通过修改原始函数来实现这一功能,但这不是一个好的实践,特别是当我们需要在多个函数中复用该功能时。
这时就可以使用装饰器来解决这个问题。下面是一个装饰器函数的简单示例:
def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling function {func.__name__} with args {args} and kwargs {kwargs}")
result = func(*args, **kwargs)
print(f"Function {func.__name__} returned {result}")
return result
return wrapper
在上面的示例中,我们定义了一个名为 logger
的装饰器函数,该函数接受一个函数作为参数,并返回一个新的函数 wrapper
。wrapper
函数包装了原始函数,打印出额外的信息,并调用原始函数本身。最后,我们返回 wrapper
函数作为装饰器的结果。
现在,我们可以将装饰器应用到我们的 add_numbers
函数上:
通过在 add_numbers
函数的定义之前添加 @logger
,我们将 add_numbers
函数作为参数传递给了 logger
装饰器。这样,每当我们调用 add_numbers
函数时,实际上是调用了 logger
装饰器返回的 wrapper
函数。
这样,每当我们调用 add_numbers
函数时,会自动打印出关于函数调用的信息。我们不需要修改 add_numbers
的定义,只需要通过装饰器来实现这一功能。
装饰器是 Python 中一种非常强大且灵活的语法,它可以用于许多场景,如日志记录、计时、缓存等。通过使用装饰器,我们可以轻松地为已存在的代码添加功能,而无需修改源代码。
使用 functools.wraps()
保持被装饰函数的元信息¶
装饰器在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数元信息会发生改变)。为了不影响原函数的元信息,Python 的functools
包中提供了一个叫wraps
的装饰器来消除这样的副作用。写一个装饰器的时候,最好在 def wrapper
之前加上 @functools.wraps(func)
,它能保留原有函数的元信息。
不加 @functools.wraps(func)
¶
def my_decorator(func):
def wrapper(*args, **kwargs):
"""decorator"""
print("Calling decorated function...")
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""Docstring"""
print("Called example function")
print("name: {}".format(example.__name__))
print("docstring: {}".format(example.__doc__))
运行结果:
加上 @functools.wraps(func)
¶
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""decorator"""
print("Calling decorated function...")
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""Docstring"""
print("Called example function")
print("name: {}".format(example.__name__))
print("docstring: {}".format(example.__doc__))
运行结果:
类装饰器¶
相比函数装饰器,类装饰更灵活,也更强大。在 Python 类中可以定义 __call__
方法,使其在无需实例化的情况下自身可以被调用,而此时就会执行 __call__
内部的代码。
class Log(object):
def __init__(self, func):
self._func = func
def __call__(self):
print("before")
self._func()
print("after")
@Log
def hello():
print("hello world!")
hello()
装饰器装饰顺序¶
一个函数其实可以同时被多个装饰器所装饰,那么多个装饰器的装饰顺序是怎样的呢?下面我们就来探索一下。
def a(func):
def wrapper():
print("a before")
func()
print("a after")
return wrapper
def b(func):
def wrapper():
print("b before")
func()
print("b after")
return wrapper
def c(func):
def wrapper():
print("c before")
func()
print("c after")
return wrapper
@a
@b
@c
def hello():
print("Hello World!")
hello()
以上代码运行结果:
多装饰的语法等效于 hello = a(b(c(hello)))
。根据打印结果不难发现这段代码的执行顺序。如果你了解过 Node.js 的 Koa2
框架的中间件机制,那么你一定不会陌生以上代码的执行顺序,实际上 Python 装饰器同样遵循 洋葱模型
。多装饰器的代码执行顺序就像剥洋葱一样,先由外到内进入,然后再由内到外。
一些实用的装饰器¶
代码计时¶
import time
from functools import wraps
def timeit(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} 函数调用耗时:{end - start:.6f}")
return result
return wrapper
@timeit
def long_running_task():
time.sleep(1)
long_running_task()
# long_running_task 函数调用耗时:1.001168
记录函数运行次数¶
Ctrl+C 终止程序需二次验证¶
Python 进阶教程系列文章