python 装饰器详解

所谓装饰器,就是在函数运行前运行的一个函数。

其中可以分为两大类的使用思路:

  1. 以装饰器包裹住主体函数
  2. 以主体函数包裹住装饰器

装饰器的本质就是@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