1.闭包学习装饰器之前,需要了解闭包的概念。形成闭包的要点:函数嵌套使用内层函数作为外层函数的返回值。内部函数必须使用外部函数的变量。下面以计算list的平均值为例来解释闭包:defmake_average():#创建一个List,用来保存值nums=[]#定义一个内部函数来计算list的平均值defaverage(n):#添加值到列表nums.append(n)#返回平均值returnsum(nums)/len(nums)returnaverage首先定义一个函数make_average;其次,在make_average函数中定义一个空列表来存储值;第三,定义一个内部函数来计算列表的平均值;最后,使用这个内部函数函数作为外部函数make_average的返回值,注意不要加(),加()就变成调用这个函数了。#调用外部函数并将其复制到一个变量中。注意:此时返回的是内部函数的内存地址a=make_average()#给这个变量加上()相当于调用内部函数averageprint(a(20))print(a(30))结果为如下:当输入值为20时,列表中只有一个数,所以计算结果为20;当输入另外一个值30时,列表中有两个数,分别是20和30,所以求平均值的结果是25。二、装饰器1.装饰器介绍例如有下面两个函数,分别计算两个数之和和分数分别为:defadd(a,b):"""计算两个数之和"""res=a+breturnresdefmul(a,b):"""计算乘积oftwonumbers"""res=a*breturnres现在有一个需求:我想在每个函数的求值开始前打印“Beginningtoevaluate...”,结束后打印“Evaluatingtoend...”的评价。我们可以通过直接修改功能代码来满足这种需求,但是这样会面临以下问题:如果要修改的功能太多,十个甚至一百个,不现实;不方便后期维护,比如我不想打印“Starttocalculating...”,而是打印“begin...”,那岂不是又要修改了;违反了开闭原则(OCP),即程序的设计要求打开程序的extension,关闭程序的Modification;因此,上述直接修改功能代码的方法是行不通的。我们希望在不修改原有功能的情况下实现功能的扩展。例如:defnew_add(a,b):print("开始计算...")r=add(a,b)print("计算结束...")returnrprint(new_add(22,33))执行结果如下:这种创建新函数的方法虽然没有修改原来的函数,但是面临一个很严重的问题:只能扩展指定的函数,不能用于其他函数。比如上面的add函数可以扩展,但是mul函数不能扩展。如果我们要扩展mul函数,只能再创建一个扩展函数;因为我们希望定义一个通用的扩展函数,可以作用于所有函数。这种不改变原函数代码的通用函数就是:装饰器。2.函数装饰器装饰器本质上是一个python函数或类,它允许其他函数或类在不修改任何代码的情况下添加额外的功能,即为现有对象添加额外的功能,装饰器的返回值也是一个函数/类目的。常用于有横切需求的场景,如:插入日志、性能测试、事务处理、缓存、权限验证等场景。1)装饰函数不带参数例如:defwrapper_info(func):definner():print("开始介绍...")res=func()print("结束介绍...")returnresreturninnerdefintroduce1():print("IamChowYun-fat,IamfromHONGKONG")info=wrapper_info(introduce1)info()运行结果如下:原函数,在原函数function的基础上增加了一些额外的函数,func是要装饰的函数,作为变量传递给被装饰的函数,可以在其他函数中使用,这个wrapper_info就是装饰器。但是我们现在面临的问题是,如果被装饰的函数有参数怎么办?例如:defintroduce2(name,age):print(f"我叫{name},我今年{age}岁")2)装饰函数有参数虽然name和age可以在装饰器中传入wrapper_info,但并不是每个被装饰的函数都只有name,age,或者指定类型的参数,可能会传入字典,列表,元组等。也就是说,如果传入参数的类型和个数不固定怎么办?这时候就需要使用变长参数:(*args,**kwargs)defwrapper_info(func):"""用来扩展其他函数,让其他函数在执行前可以做一些额外的动作:paramfunc:待扩展的函数对象:return:"""definner(*args,**kwargs):print("开始介绍...")res=func(*args,**kwargs)print("介绍结束。..")returnresreturninner例如:defintroduce3(name,age,city):print(f"我叫{name},今年{age}岁,来自{city}")运行结果如下:3)上面说的带参数的装饰器是装饰器,一个是作用于被装饰的无参函数,一个是被装饰的带参数函数,那么装饰器本身可以带参数吗?比如我定义了一个变量,我想通过传入不同的值来控制装饰器实现不同的功能。答案是肯定的,例如:defuse_log(level):defdecorator(func):definner(*args,**kwargs):iflevel=="warn":logging.warning("%sisrunningbywarning"%func.__name__)eliflevel=="info":logging.warning("%sisrunningbyinfo"%func.__name__)else:logging.warning("%sisrunningbyother"%func.__name__)returnfunc(*args,**kwargs)returninnerreturndecoratordefintroduce4(name,age,city):print(f"我叫{name},我今年{age}岁,我来自{city}")info1=use_log(introduce4('周星驰',28,'香港'))info1('信息')info2=use_log(introduce4('周润发',28,'香港'))info2('warn')info3=use_log(introduce4('成龙',28,'香港'))info3('xxx')运行结果如下:3.装饰器调用方式一:以函数方式调用info3=wrapper_info(introduce3)info3('刘德华',28,'香港')如果是带参数的装饰函数,调用方法为:info4=use_log(introduce4('周星驰',28,'香港'))info4('信息')方法二:在修饰函数上面调用语法糖,用@符号修饰@wrapper_infodefintroduce3(name,age,city):print(f"我的名字是{name},我{age}岁,我来自{city}")introduce3('刘德华',28,'香港')如果是带参数的装饰器函数,比如上面的use_log,需要传入装饰器输入参数:@use_log('info')defintroduce4(name,age,city):print(f"我叫{name},今年{age}岁,来自{city}")总结什么是装饰器?在不改变原函数代码的情况下,在原函数的基础上增加一些额外的功能,可以在其他函数中使用,这样的函数称为装饰器。装饰器的调用可以通过传统的调用函数的方式来调用,也可以使用@decorator来调用装饰器的特性,通过装饰器可以在不修改原有函数的情况下扩展功能,一个函数可以同时指定多个装饰器。
