什么是 Python 装饰器
引言
如果你能够认识到函数是一等公民(First-class)的话,那么你理解 Python 装饰器应该没有什么困难。函数是一等公民(First-class)就意味着:函数也是值,和其他基本类型(int, str, float, etc
)等一样,都可以作为函数的入参和返回值
如果一个函数的入参是某函数,或者返回值是某函数,这样的函数也叫做高阶函数,装饰器就是这样子的
在我看来,装饰器的好处是不需要修改原有的函数的情况下修改函数的功能,这也合理,毕竟它叫做装饰器
装饰器其实只是语法糖
在详细了解 Python 装饰器之前,我想告诉你的是:Python 装饰器其实是语法糖,比如下面这段代码
@some_decorator
def foobar():
...
其实等价于下面这段代码
def foobar():
...
foobar = some_decorator(foobar)
如果有多个装饰器,也是一样的道理,越靠近被装饰的函数的装饰器越早起作用
@foo
@bar
def hello():
...
# ------------------
def hello():
...
hello = foo(bar(hello))
再谈装饰器
前面提到,装饰器不过就是输入为函数,输出也为函数的高阶函数,拆解一下
- 输入为函数:函数是装饰器的入参
- 输出为函数:装饰器返回的是函数
注意,一个函数可能有参数也可能没有参数,可能有 positional argument 还可能有 keyword argument。因此,为了能够表示各种可能性,可以用 *args, **kwargs
表示。因此,经过整理,我们就能够推导出装饰器该怎么写了
def some_decorator(func):
def inner(*args, **kwargs):
# do something before the function call
func(*args, **kwargs)
# do something after the function call
return inner
让我们考虑一个更复杂的情况,如果装饰器本身有参数呢?我们可以语法糖的角度反推一下
@some_decorator(k1=v1, k2=v2)
def foobar():
...
等价于
def foobar():
...
some_decorator(k1=v1, k2=v2)(foobar)
在前面的装饰器模板中,some_decorator
的入参是被装饰的函数 func
,即 some_decorator(func)
,但现在变成了 some_decorator(k1=v1, k2=v2)
,所以我们知道:some_decorator(k1=v1, k2=v2)
应该返回一个函数。那就是多嵌套一个函数,如下所示
def some_decorator(k1, k2):
def wrapper(func):
def inner(*args, **kwargs):
# do something before the function call
func(*args, **kwargs)
# do something after the function call
return inner
return wrapper
常用装饰器
@property
面向对象编程里面会对属性(或称成员字段)进行封装,外界只能通过 getter/setter 进行访问。@property
正如它的名字一样,可以方便地访问/删除/修改属性,就好像这个属性不是私有属性,并且可以直接访问一样。比如
class Name:
def __init__(self, x: str | None = None):
self.__first_name = x
@property
def first_name(self):
return self.__first_name
@first_name.setter
def first_name(self, value: str):
self.__first_name = value
@first_name.deleter
def first_name(self, value: str):
del self.__first_name
me = Name()
print(me.first_name) # without parenthesis
me.first_name = "Martin"
print(me.first_name)
@functools.wraps
普通的装饰器存在一个缺陷,被装饰函数的名字会被修改,以前面的 some_decorator
这个装饰器为例
@some_decorator
def hello():
print("hello")
print(hello.__name__)
# inner
可以看到,被装饰的函数 hello
的名字变成了 inner
(some_decorator
里面的函数),这在 Debug 的时候不大友好,因此,可以用 @functools.wraps
import functools
def some_decorator(func):
@functools.wraps(func) # <------------
def inner(*args, **kwargs):
# do something before the function call
print("Before")
func(*args, **kwargs)
print("After")
# do something after the function call
return inner
@some_decorator
def hello():
print("hello")
print(hello.__name__)
# hello
可以看到,函数 hello
的名字被保留了
@cache
在写算法题的时候,如果我们想要缓存一个函数的计算结果,就可以用 @cache
。可以理解为它帮我们维护了一个字典,字典的 Key 是函数入参,字典的 Value 是函数的返回值
from functools import cache
@cache
def fib(n: int) -> int:
if n == 0:
return 0
elif n == 1 or n == 2:
return 1
else:
return fib(n - 1) + fib(n - 2)
@timeit
这个装饰器也很常用,写起来也很简单,这是用来测量函数耗时的
import time
import functools
def timeit(func):
@functools.wraps(func)
def inner(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print(f"Execute {func.__name__} in {end - start:.2f} seconds")
return inner
@timeit
def foobar():
ans = 0
for i in range(10000000):
ans += i
return ans
foobar()
总结
到这里本文就结束了,其实 Python 装饰器还可以用来装饰一个类,但在本文里面没有提及,因为我面向对象编程用得并不多。不过我相信,理解了 Python 装饰器如何装饰一个函数,那么你也能够理解 Python 装饰器是如何装饰一个类的