当前位置: 首页 > 后端技术 > Python

如何理解让人摇头的Python装饰器?

时间:2023-03-26 12:07:38 Python

说到Python的难点,装饰器肯定有一席之地。感觉像是在读九阴真经中的奇文。看不懂,一头雾水……怎么理解Python装饰器?回到主题,如何理解Python装饰器?装饰器可以说是Python的神器,它可以在不改变函数的代码和调用方法的情况下,为函数添加新的功能。本质上是一个嵌套函数,它将装饰函数(func)作为参数并返回一个包装函数。这样,我们就可以在不改变被装饰函数代码的情况下,为被装饰函数或程序添加新的函数。装饰器的原理组成:<函数+实参高级函数+返回值高级函数+嵌套函数+语法糖=装饰器>Python的装饰器广泛应用于:缓存权限验证(如django中的@login_required和@permission_required装饰器)性能测试(比如统计程序的运行时间),插入日志等应用场景(两种常见的入口使用日志打印机,时间定时器),有了装饰器,我们可以提取出很多无关的代码dowiththefunctionitself增加了函数的可重用性。很多初学者对装饰器理解有困难,可能是因为以下三点没有很好理解:函数“变量”(或称“可变”函数)的理解高阶函数的理解嵌套函数的理解由于装饰器的性质,以上是仍然是一个功能。如果你能一一解决上面的问题,并遵循装饰器的基本原则,相信你会对装饰器有一个很好的了解。语法糖的写法如果你接触过一段时间的装饰器,你一定对@符号不陌生。@符号是装饰器的语法糖;它被放在函数定义的开头,就像帽子戴在函数的头上一样。语法糖的写法:@装饰器名称,也可以通过语法糖完成已有功能的装饰。例如:#定义装饰器defdecorator(func):definner():#装饰内部函数中已有的函数print('已添加登录认证')func()returninner@decorator#comment=decorator(comment)装饰器语法糖封装了左边的代码comment=innerdefcomment():print('Comment')#调用方式不变功能装饰。用参数装饰importtimedeftimer(func)defdeco():start=time.time()func()stop=time.time()print(stop-start)returndeco@timerdeftest(parameter):#8time。sleep(2)print("testisrunning!")test()通常有一个实际问题的参数。如果要给#8处修改的函数加上参数,显然这个程序会报错。报错的原因是test()在调用的时候少了一个位置参数,而我们知道test=func=deco,因为test()=func()=deco(),那么当test(parameter)有参数的时候,它还必须给func()和deco()添加参数。为了使程序更具可扩展性,在装饰器中的deco()和func()中加入了可变参数agrs和*kwargs。importtimedeftimer(func)defdeco(*args,**kwargs):start=time.time()func(*args,**kwargs)stop=time.time()print(stop-start)返回deco@timerdeftest(parameter):#8time.sleep(2)print("testisrunning!")test()带参数装饰器带参数装饰器是用装饰器装饰函数,传入指定的参数;语法格式:@decorator(parameter,...)使用带参数的装饰器实际上是在装饰器外面包装了一个函数,使用这个函数接收参数,返回一个装饰器。因为@符号需要和装饰器实例一起使用。defreturn_decorator(flag):#装饰器只能接受一个参数,是函数类型defdecorator(func):definner(a,b):ifflag=='+':print('尝试进行加法计算')elifflag=='-':print('Tryingtoperformsubtraction')func(a,b)returninner#函数调用时可以返回一个装饰器returndecorator@return_decorator('+')#decorator=return_decorator('+'),@decorator=>add_num=decorator(add_num)defadd_num(a,b):result=a+bprint(result)@return_decorator('-')defsub_num(a,b):结果=a-bprint(result)add_num(1,2)sub_num(1,2)由于Python装饰器的工作原理主要依赖嵌套函数和闭包,所以我们首先要对嵌套函数和闭包有深刻的理解。嵌套函数和闭包几乎是Python求职面试的必答题;如果你说你掌握了Python,你必须知道装饰器,否则你不能说你学会了Python。还有迭代器、生成器、列表推导式、生成器表达式等,你应该了解它们。嵌套函数如果一个函数内部定义了另一个函数(注意:是定义,不是引用!),这个函数就称为嵌套函数。外部的称为外部函数,内部的称为内部函数。我们先看一个最简单的嵌套函数的例子;在外层函数中定义了一个内层函数,调用:defouter():x=1definner():y=x+1print(y)inner()outer()#OutputResult2可以看到在inner函数在自己作用域内查找局部变量失败,会在上层作用域中进一步查找。如果我们在外层函数中不直接调用内层函数,而是通过returninner返回一个内层函数的引用会怎样?您将获得一个内部函数对象而不是执行结果。defouter():x=1definner():y=x+1print(y)returninnerouter()#output.innerat0x039248E8>f1=outer()f1()#output2上面的例子比较简单,因为outer和inner函数都没有参数。我们现在在上面的代码中加入参数,可以看到可以很方便的将外层函数的参数或者变量传递给内层函数。defouter(x):a=xdefinner(y):b=yprint(a+b)returninnerf1=outer(1)#返回内部函数对象f1(10)#等价于inner(10)。输出11如果将上例中外层函数的变量x换成装饰函数对象(func),内层函数的变量y换成装饰函数的参数,就可以得到一个通用的装饰器。我们在func本身没有任何修改的情况下添加了其他函数,从而实现了函数的装饰。下面的例子:defdecorator(func):definner(*args,**kwargs):add_other_actions()returnfunc(*args,**kwargs)returninnerdecorator只返回内部函数?不!它返回的其实是一个闭包,整个装饰器的工作依赖于Python的闭包原则。Python闭包如果在外层函数中定义了一个内层函数,而内层函数体引用了函数体外的变量,那么当外层函数通过return返回内层函数的引用时,外层引用变量和函数所涉及的内层函数定义将打包成一个整体(闭包)返回。在前面的示例代码中,外部方法是否只返回内部函数对象?不对,外层函数返回的其实是一个由内层函数和外部引用变量(a)组成的闭包!defouter(x):a=xdefinner(y):b=yprint(a+b)returninnerf1=outer(1)#returninnerfunctionobject+localvariable1(closure)f1(10)#相当in内部(10)。Output11一般情况下,当一个函数运行结束时,临时变量会被销毁,但闭包是一个特例。当外层函数找到自己的临时变量时,会在以后的内层函数中使用。当它结束,返回到内层函数时,会将外层函数的临时变量绑定到内层函数。这样即使外层函数已经结束,内层函数仍然可以使用外层函数的临时变量,这就是闭包的威力。通用装饰器装饰器的外层函数会接收一个函数作为参数。该函数在内部函数内部执行。这个函数可以带参数也可以不带参数,可以有返回值也可以没有。所以装饰器也分为四类:无参数无返回值,无参数有返回值,有参数无返回值有参数有返回值,有无参数有返回值完全取决于被装饰的函数;但是,我们写装饰器的目的是为了用一个装饰器来装饰不同的功能,所以要考虑装饰器的通用性。我们使用可变参数来实现一个可以用来装饰任何函数的装饰器——通用装饰器。defdecorator_all(func):defwrapper(*args,**kwargs):print('addsomecoding')returnfunc(*args,**kwargs)returnwrapper这种装饰器可以用来装饰任何函数,不管函数是否有参数或返回值。1)装饰一个带参数的函数defdecorator(func):definner(num1,num2):print('试图进行加法计算')func(num1,num2)returninner@decoratordefadd_num(num1,num2):result=num1+num2print(f'resultis:{result}')add_num(1,2)2)用参数装饰函数,返回值defdecorator(func):definner(num1,num2):print('正在尝试执行加法计算')num=func(num1,num2)returnnumreturninner@decoratordefadd_num(num1,num2):result=num1+num2returnresultresult=add_num(1,2)print(f'resultis:{result}')output:tryingtoexecuteaddition结果是:33)修饰一个函数,参数长度可变,返回值defdecorator(func):definner(*args,**kwargs):print('tryingtoexecuteAdditioncalculation...')print("args={},kwargs={}".format(args,kwargs))#*args:传递元组中的每个元素作为位置参数#**kwargs:传递每个键值对字典作为关键字num=func(*args,**kwargs)returnnumreturninner@decoratordefadd_num(*args,**kwargs):result=0forvalueinargs:result+=valueforvalueinkwargs.values():result+=valuereturnresultresult=add_num(1,2,a=3,b=7)print(f'result:{result}')输出结果:尝试进行加法计算args=(1,2),kwargs={'a':3,'b':7}结果为:13注意:使用装饰器装饰已有函数时,内部函数的类型与待装饰的已有函数类型一致多个装饰器同时装饰一个函数defdecorator_one(func):defwrapper(*args,**kwargs):print('decoratoronestart')result=func(*args,**kwargs)print('decoratoroneend')returnresultreturnwrapperdefdecorator_two(func):defwrapper(*args,**kwargs):print('装饰器二开始')result=func(*args,**kwargs)print('装饰器二端')returnresultreturnwrapper@decorator_two@decorator_onedefhello_python():print('HelloPython!')hello_python()运行结果:decoratortwostartdecoratoronestartHelloPython!Decoratoroneenddecoratortwoend可以看到,当多个装饰器装饰同一个函数时,会是一个嵌套的装饰结果;即先执行靠近函数的装饰器,然后使用远离函数的装饰器。装饰执行结果的装饰器。再看一个例子:比如下面两个装饰器是用来装饰greeting函数的:@log@retry(10)defgreeting():print"Hello,World!"这段代码等同于:defgreeting():print"Hello,World!"temp=retry(10)(greeting)greeting=log(temp)可以看出叠加的装饰器生效的顺序是从由内而外,使用时需要特别注意。使用类定义装饰器在Python中,也可以通过类来实现装饰器。实现类装饰器时:使用__init__()方法接收装饰函数使用__call__()方法添加装饰器要实现的函数并执行并返回装饰函数类Log(在__call__()方法对象中):def__init__(self,f):self.f=fdef__call__(self,*args,**kwargs):print"before"self.f()print"after"使用类装饰器装饰函数:@Logdefgreeting():print"你好,世界!"greeting()输出与使用函数装饰器相同:beforeHello,World!after与函数装饰器相比,类装饰器具有面向对象编程支持的一系列特性,如高内聚合、封装性和灵活性。事实上,Python中的任何可调用对象都可以用来定义装饰器。总结在最后①一般来说,装饰器的作用就是给已有的对象增加额外的功能。②同时,在面向对象(OOP)设计模式中,装饰器被称为装饰模式。③OOP的装饰模式需要通过继承组合来实现,而Python除了支持OOP装饰器外,还直接从语法层面支持装饰器。④Python的装饰器可以用函数或者类来实现。内容比较详细。如果觉得有帮助,可以到gzh【Python编程学习圈】了解更多技术干货。还有大量系统的学习资料和教程可以免费获取,对大家的学习会有很大的帮助。