python装饰器的应用Date: 2021.03.14
Author: jwensh
装饰器
是Python用于封装函数或公共代码的实现方式
,来对指定的类或函数进行一个装饰,类似java中的注解
,在日常的开发过程中会经常用到,当我们使用多个装饰器装饰时,它的执行顺序是什么样的?那我们来看下。
关键结果直接给出:
装饰顺序:即python解释器执行的顺序,由下至上(运行时)调用顺序:由上至下(调用时)1.调用顺序
是自上而下
的? (实验看看)大部分涉及多个装饰器装饰的函数,调用顺序
都会认为它们是自上而下
的,比如下面这个例子
def AA(func): print('AA 1') def func_a(*args, **kwargs): print('AA 2') return func(*args, **kwargs) return func_adef BB(func): print('BB 1') def func_b(*args, **kwargs): print('BB 2') return func(*args, **kwargs) return func_b
上面代码先定义两个装饰器函数: AA
, BB
, 这两个函数实现的功能是,接收一个函数作为参数然后返回创建的另一个函数,在这个创建的函数里调用接收的函数。
@BB@AAdef f(x): print('F') return x * 10print(f(1))
定义的函数 f
采用上面定义的AA
, BB
作为装饰函数。
然后,我们以1为参数调用被装饰后的函数 f , AA, BB 的顺序是什么呢 ?
如果不假思索根据自下而上的原则来判断地话,先执行 AA 再执行 BB , 那么会先输出 AA 1
, AA 2
再输出 BB 1
, BB 2
, 但真实的实际上运行的结果如下:
AA 1BB 1BB 2AA 2F10
1.1 函数
和函数调用
的区别为什么是先执行 BB 2
再执行 AA 2
呢?
为了彻底看清上面的问题,得先分清两个概念: 函数和函数调用。上面的例子中 f 称之为函数, f(1) 称之为函数调用,后者是对前者传入参数进行求值的结果。
1.2在Python中函数也是一个对象,所以 f 是指代一个函数对象,它的值是函数本身, f(1) 是对函数的调用,它的值是调用的结果,这里的定义下 f(1) 的值10。同样地,拿上面的 AA 函数来说,它返回的是个函数对象 func_a ,这个函数对象是它内部定义的。在 func_a 里调用了函数 func ,将 func 的调用结果作为值返回。
装饰器函数
在被装饰函数定义好后立即执行
当装饰器装饰一个函数时,究竟发生了什么? 现在简化我们的例子,假设是下面这样的:
def AA(func): print('AA 1') def func_a(*args, **kwargs): print('AA 2') return func(*args, **kwargs) return func_a @AAdef f(x): print('f') return x * 2
这单个装饰器怎么理解?
@AAdef f(x): print('f') return x * 10
相当于
def f(x): print('f') return x * 10f = AA(f)
所以,当python解释器
执行这段代码时
1. AA 已经调用了,它以函数 f 作为参数, 返回它内部生成的一个函数,2. 所以此后 f 指代的是 AA 里面返回的 func_a 。3. 所以当以后调用 f 时,实际上相当于调用 func_a , 传给 f 的参数会传给 func_a ,4. 在调用 func_a 时会把接收到的参数传给 func_a 里的 func 即 f ,5. 最后返回的是 f 调用的值,所以在最外面看起来就像直接再调用 f 一样。
有点绕,可以细细体会
1.3 疑问的解释当理清上面两方面概念时,就可以清楚地看清最原始的例子中发生了什么。
当解释器执行下面这段代码时,实际上按照从下到上
的顺序已经依次调用了 AA
和 BB
,这是会输出对应的 AA 1
和 BB 1
。 这时候 f 已经相当于 BB
里的 func_b
。
但因为 f
并没有被调用,所以 func_b
并没有被调用,依次类推 func_b
内部的 func_a
也没有调用,所以 AA 2
和 BB 2
也不会被打印。
@BB@AAdef f(x): print('f') return x * 10 print(f(1))
然后最后一行当我们对 f 传入参数1进行调用时, func_b
被调用了,它会先打印 BB 2
,然后在 func_b 内部调用了 func_a
所以会再打印 AA 2
, 然后再 func_a
内部调用的原来的 f
, 并且将结果作为最终的返回。
当我们在上面的例子最后一行 print(f(1)
的调用去掉,看下结果
AA 1BB 1
由上面的实验,在实际应用的场景中,当我们采用上面的方式写了两个装饰方法, 比如先验证有没有登录@is_login
, 再验证权限够不够时 @is_permision_allowed
时,那装饰顺序:
@is_login@is_permision_alloweddef my_func() # Do something return
2. 总结其实可以这么理解,上面的函数装饰下面的函数,执行实际是自下而上,只是说 AA 执行后返回的 func_a 函数被抛给了装饰器 BB ,这里打印出函数名(func.__name__
)看看
AA 1: fBB 1: func_a开始执行之前BB 2: func_aAA 2: fF10
相当于f 函数被从下往上包装,然后再从上往下拆包装
,所以实际上装饰器内部的函数是由上往下
执行的
装饰的时候是自下而上
包装,但是装饰时我们并没有执行 f()
函数,所以是先 print 的装饰器AA , 再执行的装饰器BB,在包装完之后,f
函数被调用了,这个时候 f 的其实最终是等价于func_b的,而 func_b 是等价于 func_a 的,而 func_a 才等价于原来的 f 函数.
func_b 内容(BB 2: func_a
) 会被先打印
, 可能会迷惑为什么装饰器 AA 和 func_a 同样是 def 却不同时执行
,因为 return 一个函数名相当于是函数名的赋值,而不是函数名的调用,没有同时被调用。
AA 1, BB 1 这两行是初始化装饰器, BB 2 , AA 2 这两行是 f = BB(AA(f)) 执行闭包,第一个闭包是 BB 走到 func_b 然后执行到 AA,在 AA 执行到 func_a 再到被装饰函数 f , 是由上到下
。
以上是内存溢出为你收集整理的【测试开发】Python装饰器 - 多个装饰器的执行顺序全部内容,希望文章能够帮你解决【测试开发】Python装饰器 - 多个装饰器的执行顺序所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)