写在前面。本系列的目的:希望能过一篇,不想深究,但想在工程应用中得心应手。装饰者模式是23种著名的设计模式之一。装饰器模式可以在不改变原有代码结构的情况下扩展代码功能。Python将装饰器作为Python的一个特性,内置了对装饰器的支持,使得Python用户使用装饰器更加方便。合理使用装饰器可以让Python代码变得非常漂亮。由于设计模式是一组重复使用的代码设计经验,因此它不是编码的必要技能。所以在编码过程中,完全摒弃装饰器的使用。但是如果你不会写出没有臭味的pythonic代码,那么装饰器就是你这条路上绕不过去的一道坎。干货包括八个板块:闭包(实现装饰器的基础)、无参数函数装饰器、带参数函数装饰器、无参数类装饰器、带参数类装饰器、常用内置装饰器、装饰器总结(例程总结)、装饰器经典示例(单例模式)。闭包装饰器是通过闭包来实现的。闭包是一个相对复杂的话题。更深入一点,我们可以谈谈python处理常量表和符号表的方式。这里只是简单介绍一下。我个人认为只要记住以下三个特性,你就会理解闭包的概念。闭包是一个范围。闭包只能访问作用域内的局部变量和作用域外的非局部变量。如果作用域外存在与作用域内同名的变量var,如果在作用域内先使用变量var,再定义变量var,则报先使用再定义变量a的错误将被抛出。闭包可以“封装”一个范围。然后我们就可以在闭包外访问闭包内的局部变量了。因为局部变量被“封装”在闭包中。没有参数的函数装饰器假设有一个需求,我们需要在每个函数运行时打印当前时刻的时间戳。那么有两种写法:写一个不使用装饰器打印时间戳的实用函数,写一个业务函数。将业务功能对象传入效用函数,实现打印时间戳和执行业务功能的需求。代码如下:```importtimedeff():print("fisrunning!")deff1():print("f1isrunning!")defprint_running_time(f):print("运行时间:",time.time())f()print_running_time(f)print_running_time(f1)>>>('运行时间:',1588864281.154459)f正在运行!('runningtime:',1588864281.154483)f1isrunning!```使用装饰器(functiondecorator)写一个打印时间戳的装饰器函数,写一个业务函数。装饰器函数对业务函数进行装饰,实现打印时间戳和执行业务函数的需求。代码如下:```importtimedefprint_running_time(f):defwrapper():print("runningtime:",time.time())f()returnwrapper@print_running_timedeff():print("f正在运行!")@print_running_timedeff1():print("f1isrunning!")f()f1()>>>('runningtime:',1588864281.154459)f正在运行!('runningtime:',1588864281.154483)f1isrunning!```以上两种写法的比较通过以上两种写法的比较,我们可以发现最明显的区别就是代码运行时,第一个写法方法执行print_running_time函数,第二种写法执行f函数。那么很明显第二种写法抽象出来的语义更贴近我们的业务需求。在需求增加相同的情况下,第一种写法需要编写更多的工具函数,在执行业务功能时需要多层嵌套,大大增加了代码的复杂度。第二种写法可以在业务功能之上添加多个装饰函数进行装饰,仍然保持代码的可读性和层次性,多种需求下功能的独立性和可扩展性。装饰器原理初探装饰器的代码运行分为两步,装饰器初始化(运行到被装饰函数的定义)和被装饰函数的执行(运行到被装饰函数的调用)第二种方式写装饰器以写法为例。装饰器的原理是这样的:在代码加载过程中,代码是从上到下执行的,所以#1代码执行时,相当于#2代码被执行。(#1和#2的代码是等价的,@docorator_func装饰f,相当于执行decorator_func(f))。根据#2代码中的print_running_time,执行print_running_time(f)的返回值是wrapper(注意返回的是函数对象wrapper,不是wrapper())。#1@print_running_timedeff():print("f正在运行!")#2print_running_time(f)#3defprint_running_time(f):#3.1defwrapper():#3.2print("runningtime:",time.time())#3.3f()#3.4returnwrapper#4f()然后是源码#1处的三行代码,返回值为wrapper,相当于添加了@修饰函数,f现在指向wrapper对象。根据前面提到的闭包的特点:闭包可以访问作用域外的非局部变量,作用域可以“封装”,闭包外可以访问闭包内的变量。所以wrapper可以将#3.1中的参数f变量(修饰函数对象)访问到它的外层函数,并且可以封装wrapper作用域并保存f变量。执行#4处的业务函数f(),即执行#3.2的wrapper()代码,即执行#3.3和#3.4的代码。整个过程需要注意的是,当代码运行到#1时,f作为装饰器参数被#3.2wrapper闭包保留。#1执行完后,会有两个f对象,#4的f对象指向wrapper,#3.4的f对象还是#1的f对象。执行过程是f()==>wrapper()==>执行#7.1#7.2代码==>打印当前时间戳,成功执行原业务功能。带参数的函数装饰器现在有了新的要求。根据调试和生产环境的不同,需要开启和关闭打印时间戳的功能。这时候就需要在装饰器函数中加入参数,作为打印时间戳的开关。.如下代码所示,f()会打印当前函数的执行时间,而f1()不会打印函数importtime的执行时间#1defprint_running_time(*flag):defouter_wrapper(f):definner_wrapper():ifflag:print("runningtime:",time.time())f()returninner_wrapperreturnouter_wrapper#2@print_running_time(1)deff():print("f正在运行!")#3@print_running_time()deff1():print("f1isrunning!")f()f1()>>>('runningtime:',1588860065.265516)f正在运行!f1正在运行!带参数装饰器原理之前的简单装饰器Principle==>@decorator_func装饰业务函数f<=>decorator_func(f),然后#2处的代码<=>print_running_time(1)(f)<=>outer_wrapper(f)<=>内包装。需要注意的是,inner_wrapper作为一个闭包,包含了外层两个变量flag和f的原始值。接下来调用f(),执行inner_wrapper(),通过判断flag是true还是false来选择是否打印当前时间戳,然后执行业务函数,满足要求。无参类装饰器准确的说,装饰器的本质是将一个可调用对象作为参数传递给另一个可调用对象,然后通过闭包保存变量,在合适的时候执行。我们知道python有两个特点。Python中的函数和类是一等对象(这也是装饰器可以作为python特性的原因之一)。如果callable(obj)在python中为真,则该对象是可调用的。所以实现了__call__魔术方法的类、函数、方法和类实例都是可调用对象。根据装饰器的性质和Python的上述两个特点,可以得出以下结论:函数和类都可以作为装饰器,也可以被装饰器装饰。类装饰器与函数装饰器具有相同的思想。__init__是对象初始化的第一步,可以达到一层闭包的效果。将前面的简单函数装饰器示例替换为类装饰器。代码如下(为了和前面的代码保持一致,所以类名不符合Python命名规范):```importtimeclassprint_running_time:def__init__(self,f):#相当于一个闭包,通过实例属性保存变量f实现闭包中的变量封装self.f=fdef__call__(self):#类实例可以调用print("runningtime:",time.time())returnself.f()@print_running_timedeff():print("fisrunning!")@print_running_timedeff1():print("f1isrunning!")f()f1()>>>输出与simplefunctiondecorator```Classdecoratorwithparameters将之前simplefunctiondecorator的例子换成classdecorator,代码如下:importtimeclassprint_running_time:def__init__(self,*flag):#相当于一个闭包,保存变量flag通过实例属性实现闭包中的变量封装self.flag=flagdef__call__(self,f):#可以调用类实例,传入业务函数fdefwrapper():ifself.flag:print("runningtime:",time.time())f()returnwrapper@print_running_time(1)deff():print("f正在运行!")@print_running_time()deff1():print("f1正在运行!")f()f1()>>>带参数的输出函数装饰器。常用的内置装饰器。装饰器是Python最重要的特性之一。Python实现了很多对装饰器的支持。__doc__如下代码所示,wrap装饰器的切换会导致在f.__doc__中打印两个结果如果#1.1的代码被注释掉,则打印的结果为#1.3如果添加#1.1的代码,打印结果是#2.1importtimefromfunctoolsimportwraps#1defprint_running_time(f):@wraps(f)#1.1defwrapper():'''thefuncwrapper'''#1.3print("runningtime:",time.time())f()returnwrapper#2@print_running_timedeff():'''funcf'''#2.1print("fisrunning!")print(f.__doc__)>>>funcfproperty,setter,deleter一个是孪生兄弟,其中property用的最多,setter和deleter是附属于property的。property:将函数调用转换为属性setter:设置属性deleter:删除属性类似于JavaBean,可以将对属性的操作写到函数中,限制对属性的操作,保护属性的安全。代码如下:classStudent(object):@propertydefname(self):returnself._name@name.setterdefname(self,name):iflen(name)<2:raiseValueError("Unknownhero")self._name=name@name.deleterdefname(self):delself._namestu=Student()stu.name="Liu"#name.setterprint(stu.name)##propertydelstu.name#name.deleterprint(stu.name)#raiseAttributeError多装饰器叠加多装饰器叠加是python中很常见的操作,比如Flask和Django,例如:importsys#1deff1(func):print('f1start')defwrapper():#1.1print('f1'+sys._getframe().f_code.co_name+'start')func()#1.2print('f1'+sys._getframe().f_code.co_name+'end')print('f1end')returnwrapper#2deff2(func):print('f2start')defwrapper():#2.1print('f2'+sys._getframe().f_code.co_name+'开始')func()#2.2print('f2'+sys._getframe().f_code.co_name+'end')print('f2end')returnwrapper#3@f1@f2deffunc():#3.1print('thefunc')#4func()#4.1>>>f2startf2endf1startf1endf1wrapperstartf2wrapperstartfuncf2wrapperendf1wrapperend多装饰器执行流程分析执行分为两步,装饰器初始化,装饰器函数执行顺序如下:装饰器初始化,根据装饰器原理,#3处的代码相当于f1(f2(func))执行f2(func);>>>f2开始f2结束;returnwrapperat#2.1(#2.2Thefuncat#3.1isthefuncat#3.1)executef1(f2(func))==>f1(wrapperat#2.1);>>>f1开始f1结束;returnwrapperat#1.1(funcat#1.2Theinitializationofthewrapper)decoratorat#2.1结束,以上两步的输出如下:>>>f2startf2endf1startf1end由decoratedfunction:func()<=>wrapperat#1.1,替换代码如下print('f1'+sys._getframe().f_code.co_name+'start')func()#1.2print('f1'+sys._getframe().f_code.co_name+'end')\#1.2func<=>#2.1处的wrapper,替换后代码如下```print('f1'+sys._getframe().f_code.co_name+'start')print('f2'+sys._getframe().f_code.co_name+'start')func()#2.2print('f2'+sys._getframe().f_code.co_name+'end')print('f1'+sys._getframe().f_code.co_name+'end')```\#2.2处的func就是#4.1处的func,执行上面的代码,输出结果如下:```>>>f1wrapperstartf2wrapper启动函数f2wrapperendf1wrapperend```Decorator总结了装饰器的原理:#1代码等价于#2代码#1@decorator_funcdeffunc():pass#2decorator_func(func)装饰器例程一个没有参数的函数装饰器需要有两层函数:外部函数参数是修饰函数对象的参数。内部参数是修饰函数的参数。带参数的函数装饰器需要有三层函数:外层函数参数是装饰器函数参数(废话,外层函数本来就是Decorator函数)中间层函数参数是被装饰的函数对象,内层函数参数是被装饰函数的参数类装饰器。同理,最外层函数可以换成__init_函数,中间层(如果有内层函数写在_call__中叠加多个装饰器。根据业务功能和装饰器函数的距离,装饰器函数(外函数)由近到远执行,然后内函数由远到近执行。装饰器经典示例:单例模式下单进程可行,多线程需要加锁单例模式#eg:1classSingleton:_singleton=Nonedef__new__(cls):ifcls._singletonisNone:cls._singleton=super().__new__(cls)returncls._singletonins1=Singleton()ins2=Singleton()print(ins1isins2)#eg:2defsingleton(cls):ins_pool={}definner():ifclsnotinins_pool:ins_pool[cls]=cls()returnins_pool[cls]returninner@singletonclassCls:def__init__(self):passins1=Cls()ins2=Cls()print(ins1isins2)#例如:3classSingleton:def__init__(self,cls):self.ins_pool={}self.cls=clsdef__call__(self):print(self.ins_pool)如果self.cls不在self.ins_pool:self.ins_pool[self.cls]=self.cls()returnself.ins_pool[self.cls]@SingletonclassCls:def__init__(self):passins1=Cls()ins2=Cls()print(ins1isins2)写在最后,希望大家掌握杀手装饰器通过这篇文章的特点。欢迎来到我的个人博客:姚少敏的博客
