所谓装饰器,就是在函数运行前运行的一个函数。
其中可以分为两大类的使用思路:
- 以装饰器包裹住主体函数
- 以主体函数包裹住装饰器
装饰器的本质就是@magic == (extend = magic(extend))
按照python的设计思路,其实应该多用装饰器方法,少用类
- Typer这个库非常好的使用了装饰器,可以做为参考
以装饰器包裹住主体函数的基本型
@magic
def extend()
这个装饰器其实运行的就是将下面两行整合了
extend = magic(extend)
extend()
所以可以推导出装饰器的基本型
# 装饰器基本型
def magic(func):
print('in magic')
return func
@magic # extend_magic = magic(extend_magic)
def extend_magic():
print('in extend_magic')
extend_magic
#output:
#in magic
从这个基本型可以看出,在没有执行函数的时候,其实是已经执行了magic(func)
的。
那么现在我们想在没有执行extend_magic
的时候,不要执行magic
中的内容要如何呢?
不预执行magic
修改magic函数为:
def magic(func):
def _():
print('in magic')
func()
return _
这样所谓的闭包,在没有执行的时候,返回的就只是_
这个函数。
所以执行extend_magic()
后的结果为:
in magic
in extend_magic
要仔细理解这里的
return
, 理解了这里就可以理解@property
装饰器。python 描述符详解
以主体函数包裹住装饰器的基本型
def magic(func):
print('in magic')
func()
def extend_magic():
print('in extend_magic')
@magic
def extend():
print('in extend')
extend_magic()
# output:
#in extend_magic
#in magic
#in extend
可以看到,这种使用方式就可以让装饰器在函数中间的某个地方执行,并且没有使用闭包的方式,因为可以不用担心在只调用对象的而不执行的地方错误的执行magic
函数。
主函数带参数的基本型
上面的基本型有一个问题,执行语句func()
在装饰器中,是不带参数的,那么要带参数应该如何呢?
基本想法是将主函数的属性复制到装饰器中。这里就要引入from functools import wraps
,这个包装器了,作用就是复制原函数的属性,当然也就包含了参数值。
python3后可以不需要引入wrpas包,也能获取到函数的参数值
def first(func):
print("init first method")
def wrapper(*args, **kargs):
print("call first method start")
func(*args, **kargs)
print("call first method end")
return wrapper
@first
def func(a, b):
print("call func, sum: ", sum((a, b)))
func(1, 2)
# output:
#init first method
#call first method start
#call func, sum: 3
#call first method end
等价原型为
first(func)(1, 2)
固定用法:
def _(*args, **kwargs):
return func(*args, **kwargs)
return _
多装饰器的执行顺序
预执行顺序是由内而外的,理解的是距离主体越近越提前执行。
def first(func):
print("init first method")
def wrapper():
print("call first method start")
func()
print("call first method end")
return wrapper
def second(func):
print("init second method")
def wrapper():
print("call second method start")
func()
print("call second method end")
return wrapper
@first
@second
def func():
print("call func")
print("start program")
func()
# output:
#init second method
#init first method
#start program
#call first method start
#call second method start
#call func
#call second method end
#call first method end
等价原型为:
first(second(func))()
带参数的装饰器的基本型
现在,想要对装饰器加上参数,如何做。
首先我们要知道,带参数的装饰器与不带参数的装饰器,在行为上是不一样的。
不带参数的装饰器是这样的:
@decorate
def func():
pass
#等效
func = decorate(func)
而带参数的装饰器是这样的:
@decorate(a,b)
def func():
pass
# 等效
func = decorate(a,b)(func)
由此可以看出,带参数的其实经过了两次调用,所以,我们也必须要装饰器中给出两次调用。
所以基本型是如下:
def first(a, b):
print("init first method")
def wrapper(func):
print("init first_wrapper method")
def _():
print("call first method start")
print("first method args: ", a, b)
func()
print("call first method end")
return _
return wrapper
@first(1, 2)
def func():
print("call func")
print("== start program ==")
func()
# output:
#init first method
#init first_wrapper method
#== start program ==
#call first method start
#first method args: 1 2
#call func
#call first method end
等效于
first(1, 2)(func)()
主函数与装饰器都有参数
要想都有参数,其实和上面已经一样了,只是多出了调用参数的环节
def first(a, b):
print("init first method")
def wrapper(func):
print("init first_wrapper method")
def _(*args, **kwargs):
print("call first method start")
print("first method args: ", a, b)
func(*args, **kwargs)
print("call first method end")
return _
return wrapper
@first(1, 2)
def func(c, d):
print("call func, args: ", c, d)
print("== start program ==")
func(3, 4)
等效于
first(1, 2)(func)(3, 4)
使用yield
def first(a, b):
print("init first method")
def wrapper(func):
print("init first_wrapper method")
def _(*args, **kwargs):
print("call first method start")
print("first method args: ", a, b)
for i in func(*args, **kwargs):
print("sum: ", i)
print("call first method end")
return _
return wrapper
@first(1, 2)
def func(c, d):
yield sum((c, d))
print("== start program ==")
func(3, 4)
# output:
#init first method
#init first_wrapper method
#== start program ==
#call first method start
#first method args: 1 2
#sum: 7
#call first method end
类方法装饰器
同一个类中,方法使用装饰器
class Connect():
def check_conn(func):
"""检查连接状态"""
def wrapper(self, *args, **kwargs):
if self.is_connected():
return func(self, *args, **kwargs)
else:
raise ConnectException("Not connected")
return wrapper
@check_conn
def disconnect(self):
self.handle.disconnect(self.conn)
self.conn = None
在类外使用类中装饰器
class Before():
def command(self):
def wapper(f):
print('command before')
return f
return wapper
app = Before()
@app.command()
def hello():
print('hello')
hello()
# output:
#command before
#hello
使用__call__
魔法方法触发装饰器
class MyDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("--- checking ---")
return self.func(*args, **kwargs)
@MyDecorator
def check_2019_nCov(name):
print(f"{name} is very healty")
if __name__ == '__main__':
check_2019_nCov("youge")
# output
--- checking ---
youge is very healty
总结
装饰器不带参数的基本型:
def decorator(func):
# do some decorator init
def fun_main(*args, **kwargs):
...
func(*args, **kwargs)
...
return func_main
@decorator
def func(*args, **kwargs):
...
func(a, b)
# 等效于
decorator(func)(a, b)
装饰器带参数的基本型:
def decorator(*dec_args, **dec_kwargs):
# do some decorator init
def func_init(func):
# do some func init
def func_main(*func_args, **func_kwargs)
...
func(*func_args, **func_kwargs)
...
return func_main
return func_init
@decorator(a, b)
def func(*func_args, **func_kwargs):
...
func(c, d)
# 等效于
decorator(a, b)(func)(c, d)
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 365433079@qq.com