装饰器
-
装饰器(Decorator):
-
虚拟场景
-
有一个大公司,下属的基础平台部负责内部应用程序及API的开发。另外还有上百个业务部门负责不同的业务,这些业务部门各自调用基础平台部提供的不同函数,也就是API处理自己的业务,情况如下:
-
#基础平台部门开发了上百个函数的API def f1(): print('业务部门1的数据接口......') def f2(): print('业务部门2的数据接口......') def f3(): print('业务部门3的数据接口......') def f100(): print('业务部门100的数据接口......') #各部分分别调用自己部分的API f1() f2() f3() f100()
-
公司还在创业初期时,基础平台部就开发了这些函数。由于各种原因,比如时间紧,比如人手不足,比如架构缺陷,比如考虑不周等等,没有为函数的调用进行安全认证。现在,公司发展壮大了,不能再像初创时期的“草台班子”一样将就下去了,基础平台部主管决定弥补这个缺陷,于是:
-
第一天:主管叫来了一个运维工程师,工程师跑上跑下逐个部门进行通知,让他们在代码里加上认证功能,然后,当天他被开除了。
-
第二天:主管叫来了一个python自动化开发工程师。哥们是这么干的,只对基础平台的代码进行重构,让N个业务部门无需做任何修改。这哥们很快也被开了,连运维也没得做。
-
#基础平台部门开发了上百个函数的API def f1(): #加入认证程序代码 xxx print('业务部门1的数据接口......') def f2(): #加入认证程序代码 xxx print('业务部门2的数据接口......') def f3(): #加入认证程序代码 xxx print('业务部门3的数据接口......') def f100(): #加入认证程序代码 xxx print('业务部门100的数据接口......') #各部分分别调用自己部分的API f1() f2() f3() f100()
-
第三天:主管又换了个开发工程师。他是这么干的:定义个认证函数,在原来其他的函数中调用它,代码如下:
-
#基础平台部门开发了上百个函数的API def cheak(): pass def f1(): cheak() print('业务部门1的数据接口......') def f2(): cheak() print('业务部门2的数据接口......') def f3(): cheak() print('业务部门3的数据接口......') def f100(): cheak() print('业务部门100的数据接口......') #各部分分别调用自己部分的API f1() f2() f3() f100()
-
但是主管依然不满意,不过这一次他解释了为什么。
-
第四天:已经没有时间让主管找别人来干这活了,他决定亲自上阵,使用装饰器完成这一任务,并且打算在函数执行后再增加个日志功能。主管的代码如下:
-
def outer(func): def inner(): print('认证功能操作') result = func() return result return inner #基础平台部门开发了上百个函数的API @outer def f1(): print('业务部门1的数据接口......') @outer def f2(): print('业务部门2的数据接口......') @outer def f3(): print('业务部门3的数据接口......') @outer def f100(): print('业务部门100的数据接口......') #各部分分别调用自己部分的API f1() f2() f3() f100()
-
使用装饰器@outer,也是仅需对基础平台的代码进行拓展,就可以实现在其他部门调用函数API之前都进行认证操作,并且其他业务部门无需对他们自己的代码做任何修改,调用方式也不用变。
-
-
-
装饰器机制分析
-
下面以f1函数为例,对装饰器的运行机制进行分析:
-
pass
-
@outer和@outer()有区别,没有括号时,outer函数依然会被执行,这和传统的用括号才能调用函数不同,需要特别注意!
-
f1这个函数名当做参数传递给装饰函数outer,也就是:func = f1,@outer等于outer(f1),实际上传递了f1的函数体,而不是执行f1后的返回值。
-
outer函数return的是inner这个函数名,而不是inner()这样被调用后的返回值
-
4.程序开始执行outer函数内部的内容,一开始它又碰到了一个函数inner,inner函数定义块被程序观察到后不会立刻执行,而是读入内存中(这是默认规则)。
-
5.再往下,碰到return inner,返回值是个函数名,并且这个函数名会被赋值给f1这个被装饰的函数,也就是f1 = inner。根据前面的知识,我们知道,此时f1函数被新的函数inner覆盖了(实际上是f1这个函数名更改成指向inner这个函数名指向的函数体内存地址,f1不再指向它原来的函数体的内存地址),再往后调用f1的时候将执行inner函数内的代码,而不是先前的函数体。那么先前的函数体去哪了?还记得我们将f1当做参数传递给func这个形参么?func这个变量保存了老的函数在内存中的地址,通过它就可以执行老的函数体,你能在inner函数里看到result = func()这句代码,它就是这么干的!
-
6.接下来,还没有结束。当业务部门,依然通过f1()的方式调用f1函数时,执行的就不再是旧的f1函数的代码,而是inner函数的代码。
-
7.以上流程走完后,你应该看出来了,在没有对业务部门的代码和接口调用方式做任何修改的同时,也没有对基础平台部原有的代码做内部修改,仅仅是添加了一个装饰函数,就实现了我们的需求,在函数调用前进行认证,调用后写入日志。这就是装饰器的最大作用。
-
-
思考:为什么我们要搞一个outer函数一个inner函数这么复杂呢?一层函数不行吗?
-
-
带参装饰器
-
装饰器高级进阶(选学)
-
一个函数可以被多个装饰器装饰吗?
-
def decorator_a(func): print('this is decorator_a') def inner_a(*args, **kwargs): print('this is inner_a') return func(*args, **kwargs) return inner_a def decorator_b(func): print('this is decorator_b') def inner_b(*args, **kwargs): print('this is inner_b') return func(*args, **kwargs) return inner_b @decorator_b @decorator_a def f(x): print('this is f') return x * 2 f(1)
-
-
为何会有这样的执行结果呢?那么多个装饰器的执行顺序到底是怎样的呢?
-
装饰顺序
-
#没有调用函数的情况
-
-
就近原则