2001年,Python 2.2 引入了基于object的新式类,同时引入两个内置函数staticmethod和classmethod:
| class C(object):
def hello(arg1, arg2):
...
hello = staticmethod(hello)
def debao(cls, arg1, arg2):
...
debao = classmethod(debao)
|
为了使写法更优雅,2004年发布的 Python 2.4 引入了装饰器(Decorator):
| class C(object):
@staticmethod
def hello(arg1, arg2):
...
@classmethod
def debao(cls, arg1, arg2):
...
|
自此,装饰器在python中的使用越来越广
简单装饰器
如前所述,用于函数或方法的装饰器是 Python2.4 引入的,详见 PEP 318。后来 Python 3.0 时引入了用于类的装饰器,详见 PEP 3129。
简单来说,装饰器是一个函数:
- 它接受一个函数作为输入并返回一个函数作为输出
- 返回的函数可以与输入函数相同,也可以是不同的函数
- 装饰器可以用于为输入函数添加附加功能而不修改输入函数
如果我们要给下面函数写一个装饰器:
| def hello(name):
print('hello', name)
|
由于函数在python中是一等公民,可以作为对象来传来传去,只需要这么写:
| def decorator(func):
def wrapper(*args, **kwargs):
print('before')
func(*args, **kwargs)
print('after')
return wrapper
|
而后就可以使用了:
| @decorator
def hello(name):
print('hello', name)
hello('debao')
|
结果:
语法糖
首先装饰器是一个语法糖,如果不含糖,上面的写法就是:
| hello = decorator(hello)
hello('debao')
|
或者:
| decorator(hello)('debao')
|
内嵌函数、闭包(closure)
如果在一个内嵌函数中,对定义它的外部函数的作用域中的变量(甚至是外层之外,只要不是全局变量,也即内嵌函数中还可以嵌套定义内嵌函数)进行了引用,那么这个子函数就被认为是闭包。
定义装饰器时,首先定义了一个内嵌函数,而且该函数还访问了自身作用域外的变量 func。换句话说,它是一个闭包。
| def decorator(func):
def wrapper(*args, **kwargs):
print('before')
func(*args, **kwargs)
print('after')
return wrapper
|
注意,这个内嵌函数叫什么名字无所谓,只需要参数和待装饰函数的一致就好了。
装饰器类
除了可以用函数定义装饰器,还可以用类来定义装饰器:
1
2
3
4
5
6
7
8
9
10
11
12 | class decorator_class:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('before')
self.func(*args, **kwargs)
print('after')
@decorator_class
def hello(name):
print('hello', name)
|
调用函数hello('debao')等价于:
| decorator_class(hello)('debao')
|
挺简单直接的
装饰类方法
用法和装饰普通函数一样。但装饰类方法时,需要记得处理self参数。
| class Hello:
def __init__(self, name):
self.name = name
@decorator
def say(self):
print('hello', self.name)
|
前面我们参数类型用的 *args, **kwarg
,直接隐藏了这个问题:
| def decorator(func):
def wrapper(*args, **kwargs):
...
|
不然就需要:
| def decorator(func):
def wrapper(self):
print('before')
func(self)
print('after')
return wrapper
|
带参数装饰器
要让装饰器带参数,只需要外边再包一层即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | def count(c: int):
def decorator(func):
def wrapper(*args, **kwargs):
print('before')
for i in range(c):
func(*args, **kwargs)
print('after')
return wrapper
return decorator
@count(3)
def hello(name):
print('hello', name)
hello('debao')
|
结果如下:
| before
hello debao
hello debao
hello debao
after
|
也很直观,函数就是这么调用的:
如果带参数的装饰器使用类定义,也是需要在外边套一层(效果是原来__init__
的参数给了__call__
,call里面使用闭包:
| class Count:
def __init__(self, c: int):
self.c = c
def __call__(self, func):
def wrapper(*args, **kwargs):
print('before')
for i in range(self.c):
func(*args, **kwargs)
print('after')
return wrapper
|
类装饰器
前面的装饰器,不管是用函数定义,还是用类定义,都是用于装饰函数的。PEP 3129 引入用于装饰类的装饰器。
类在Python中也是对象,可以写一个函数,输入是一个类,输出也是一个类:
1
2
3
4
5
6
7
8
9
10
11
12 | def add_say_method(cls):
def say(self):
print('hello', self.name)
cls.say = say
return cls
@add_say_method
class Hello:
def __init__(self, name):
self.name = name
Hello('debao').say()
|
在这个例子中,我们给Hello类通过装饰器,添加了一个新的成员say。
如果去糖,则有:
| add_say_method(Hello)('debao').say()
|
这儿,装饰器也可以用类来定义,也可以为装饰器添加参数。原理和用于函数的装饰器都是一样的。
装饰器嵌套
装饰器可以嵌套使用
| @d1
@d2
def hello():
pass
|
如果一个函数需要被多个装饰器装饰,执行顺序如何?:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | def outer(c: int):
print ('outer enter')
def decorator(func):
def wrapper(*args, **kwargs):
print('outter before func')
for i in range(c):
func(*args, **kwargs)
print('outter after func')
return wrapper
print ('outer leave')
return decorator
def inner(func):
print('inner enter')
def wrapper(*args, **kwargs):
print('inner before func')
func(*args, **kwargs)
print('inner after func')
print('inner leave')
return wrapper
@outer(3)
@inner
def hello(name):
print('hello', name)
hello('debao')
|
结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | outer enter
outer leave
inner enter
inner leave
outter before func
inner before func
hello debao
inner after func
inner before func
hello debao
inner after func
inner before func
hello debao
inner after func
outter after func
|
看结果稍微凌乱,但是执行顺序也简单:
| outer(3)(inner(hello))('debao')
|
装饰器副作用
装饰器促进了代码复用,但是装饰后会丢失一些信息,比如函数的__name__
等。
1
2
3
4
5
6
7
8
9
10
11
12 | def decorator(func):
def wrapper(*args, **kwargs):
print('before')
func(*args, **kwargs)
print('after')
return wrapper
@decorator
def hello(name):
print('hello', name)
print (hello.__name__ )
|
结果并不是我们想要的:
functools 模块的 wraps 用于缓解这种问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | import functools
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('before')
func(*args, **kwargs)
print('after')
return wrapper
@decorator
def hello(name):
print('hello', name)
print (hello.__name__ )
|
使用了wraps装饰器。
内置装饰器
具体清单见:
- https://wiki.python.org/moin/Decorators
内置(builtin)装饰器:
- staticmethod
- classmethod
- property
库中的装饰器:
- functools.wraps
- abc.abstractmethod: PEP 3119
- dataclasses.dataclass:PEP 557
- contextlib.contextmanager:PEP 343
typing.dataclass_transform
:PEP 681
- ...
- contextlib.asynccontextmanager
- ...
property
propery 用以将类中定义的对象方法变成属性,可以直接对属性赋值或读取:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | class Student:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def info(self):
return self.name + ' ' + str(self.age)
@info.setter
def info(self, name_age):
self.name, self.age = name_age.split(' ')
@info.deleter
def info(self):
del self.name
del self.age
def __str__(self):
return self.name + ' ' + str(self.age)
s = Student('1+1=10', 40)
print(s.info)
|
注意:property修饰的这几个方法的命名必须相同,getter对应的方法总是用 @property
修饰,其他两个为属性名加上 .setter
和 .deleter
,如果属性只读,不用定义 setter 方法。
functools 中定义了好多装饰器。不过 wraps 是其中出镜频率最高的。
除此之外,还有:
- functools.cache
functools.cached_property
functools.lru_cache
functools.total_ordering
:类装饰符,被装饰的类必须实现__eq__
以及一个不等操作。
- functools.singledispatch
- ....
可以看几个例子。
- 使用cache缓存程序的结果,比如:
| import functools
@functools.cache
def factorial(n):
return n * factorial(n-1) if n else 1
factorial(5)
print(factorial.cache_info())
|
结果
| CacheInfo(hits=0, misses=6, maxsize=None, currsize=6)
|
- 使用singledispatch,感觉这个像C++中的模板或特化,比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | import functools
@functools.singledispatch
def add(a, b):
raise NotImplementedError('Unsupported type')
@add.register(int)
def _(a, b):
print('First argument is of type int')
print(a + b)
@add.register(str)
def _(a, b):
print('First argument is of type str')
print(a + b)
@add.register(list)
def _(a, b):
print('First argument is of type list')
print(a + b)
add(1, 2) # OK
add('a', 'b') # OK
add([1, 2], [3, 4]) # OK
add(1.0, 2.0) # NotImplementedError
|
结果:
| First argument is of type int
3
First argument is of type str
ab
First argument is of type list
[1, 2, 3, 4]
Traceback (most recent call last):
...
NotImplementedError: Unsupported type
|
abc.abstractmethod
Python3.0引入抽象基类abc(Abstract Base Classes),对应PEP 3119。同时引入了包括abstractmethod在内的多个装饰器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | from abc import ABCMeta, abstractmethod
class A(metaclass=ABCMeta):
@abstractmethod
def foo(self): pass
A() # raises TypeError
class B(A):
pass
B() # raises TypeError
class C(A):
def foo(self):
print(42)
C() # works
|
注意:装饰器abstractmethod,只能用在类内部,且该类的metaclass必须是ABCMeta或其派生类。
使用装饰器后,方法的__isabstractmethod__
返回True:
| from abc import ABCMeta, abstractmethod
class A(metaclass=ABCMeta):
@abstractmethod
def foo(self): pass
print (A.foo.__isabstractmethod__)
|
另外,abc中其他几个装饰器:
- abstractproperty
- abstractstaticmethod
- abstractclassmethod
都已经废弃,不建议使用。
dataclass
例子:
| from dataclasses import dataclass
@dataclass
class User:
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
|
dataclass装饰器可以有参数:
| @dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
|
- init:控制是否生成
__init__
方法;
- repr:控制是否生成
__repr__
方法;
- eq:控制是否生成
__eq__
方法,它用于判断实例是否相等;
- order:控制是否创建四种大小关系方法:
__lt__
、__le__
、__gt__
、__ge__
;order为True,则eq不能为False,也不能自定义order方法。
unsafe_hash
:控制hash的生成方式。
- frozen:控制是否冻结对field赋值。设置为True时,对象将是不可变的,
__setattr__
、__delattr__
将会导致TypeError
错误。
contextlib.contextmanager
with 语句相关
1
2
3
4
5
6
7
8
9
10
11
12 | class C:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
return True
with C() as a:
print('hello')
|
使用装饰器:
| @contextmanager
def A():
print('enter')
yield
print('exit')
with A() as a:
print('hello')
|
结果:
注:与此类似,和 async with 配合的装饰器是 contextlib.asynccontextmanager
。
参考