python中的装饰器是干嘛的,python装饰器是什么,他有什么用
https://www.toutiao.com/a6714102487055335949/
Decorator是Python中的一个特殊工具,它为我们提供了在函数之外修改函数的灵活能力。有点像带有独特@符号的魔法帽。只要把它戴在函数的上面,就可以悄悄地改变函数本身的行为。
你可能和装修工打过很多交道。在做面向对象编程的时候,我们经常会用到两个内置的decorators @ staticmethod和@classmethod。另外,如果你接触过click模块,对装饰器也不会陌生。Click最著名的参数定义界面@click.option(.)是用decorator实现的。
除了使用装饰品,我们还经常需要自己写一些装饰品。在这篇文章中,我将从最佳实践和常见错误两个方面与大家分享一些关于装修工的知识。
最佳实践
1. 尝试用类来实现装饰器
大多数装饰器都是基于函数和闭包实现的,但这并不是制造装饰器的唯一方式。其实Python对于一个对象是否可以以装饰者(@decorator)的形式使用只有一个要求:decorator 必须是一个“可被调用(callable)的对象。
#使用callable可以检测一个对象是否“可调用”。Def foo():通过.type(foo)class function callable(foo)true function自然是一个“可调用”的对象。但除了函数,我们还可以让任何类“可调用”。方法很简单,自定义类的__call__ magic方法即可。
class foo:def _ _ call _ _(self):print( Hello,_ _ call _ _ )foo=foo()# OUTPUT:true print(callable(foo))# call foo实例# OUTPUT: Hello,__call__foo()基于这个特性,
下面的代码定义了一个名为@delay(duration)的装饰器,被装饰的函数将在每次执行前等待额外的持续时间秒。同时,我们也希望为用户提供不需要等待立即执行的eager_call接口。
import time import func tools class delay func:def _ _ init _ _(self,duration,func):self . duration=duration self . func=func def _ _ call _ _(self,*args,**kwargs): print(f 等待{self.duration}秒.)time . sleep(self . duration)return self . func(*args,**kwargs) def eager_call(self,* args,* * kwargs):print( call without delay )return self . func(* args,* * kwargs)def delay(duration): Decorator:延迟一个函数的执行。同时,提供。eager_call方法立即执行 #这里为了避免定义额外的函数,使用functools.partial来帮助构造# DelayFunc实例的示例代码returnfunctools . partial(delay func,duration)如何使用装饰器:
@ delay (duration=2) DEF ADD (A,B): RETURN AB #此调用将延迟2秒add(1,2)#此调用将立即执行。补充。EAGER _ CALL (1,2) @ delay (duration)是一个基于类的装饰器。当然,如果你非常熟悉Python中的函数和闭包,上面的delay decorator实际上只能由函数来实现。那么,我们为什么要用类来做这件事呢?
与纯函数相比,我认为用class实现的decorator在特定场景下有几个优势:
在实现有状态装饰器时,操作类属性比在闭包中操作变量更直观,而且不会出错。当用函数扩展接口实现装饰器时,使用类包装函数更容易维护和实现与装饰器和上下文管理器协议都兼容的对象(参考unitest.mock.patch)2. 使用 wrapt 模块编写更扁平的装饰器。
在编写decorator的过程中,有没有遇到过不愉快的事情?不管你有没有,反正我有。我经常写代码的时候,特别不适应下面两件事:
当用参数实现装饰器时,嵌套的函数代码尤其难以编写和读取。由于函数和类方法的不同,为前者编写的装饰器不能直接应用于后者。例如,在下面的例子中,我实现了一个装饰器,它生成随机数并将它们注入到函数参数中。
import random def provide _ num(min _ num,max _ num): Decorator:在[min_num,max_num]范围内随机生成一个整数并追加为第一个位置参数 def wrapper(func):def decorated(* args,* * kwargs):num=random . randint(min _ num,max _ num) # Append num作为第一个参数并调用函数return func(num,* args,**kwargs) return decorated返回wrapper@provide_number(1, 00)def print _ random _ number(num):print(num)# output一个1-100的随机整数# output:72 print _ random _ number()@ provide _ number decorator函数看起来很不错,但是它有两个我前面提到的问题:嵌套层级深、无法在类方法上使用。如果直接用它来修饰类方法,
class Foo: @provide_number(1,100)def print _ random _ number(self,Num):print(Num)# output:_ _ main _ _。fooobjectat0x104047278foo()。foo类实例中的print_random_number方法将输出类实例本身,而不是预期的随机数num。
造成这种结果的原因是类方法和函数的工作机制略有不同。要修复这个问题,provider_number decorator在修改class方法的position参数时,必须智能地跳过隐藏在*args中的class实例的self变量,以便正确地注入num作为第一个参数。
这个时候应该是wrapt模块登场的时候了。Wrapt module是一个工具库,专门用来帮助你编写decorators。有了它,我们可以非常方便地改造provide_number decorator,完美解决“嵌套层次深”和“无法通用”这两个问题。
import wrapptdef provide _ num(min _ num,max _ num):@ wrapt . decorator def wrapper(wrapped,instance,args,Kwargs): #参数含义:#-wrapped:修饰的函数或class method #-instance:#-如果被修饰的人是普通的类方法,则值为类实例#-如果被修饰的人是类方法,则值为class #-如果被修饰的人是类/函数/静态方法, 值为None # #-args:调用时的position参数(注意没有*号)#-kwargs:调用时的keyword参数(注意没有* *号)# num=random.randint (min _ num,max _ num) #无需注意wrapped是类方法还是普通函数,直接在头中追加参数args=(num,)args return wrapper(* args,* * kwargs) return wrapper .省略了应用装饰器的一些代码.# output: 48foo()。print _ random _ number()用包装器模块编写的装饰器与原来的相比有以下优点:
嵌套层数少:使用@ wrap.decorator将两层嵌套减少为一层更简单:在处理位置和关键字参数时,可以忽略类实例等特例,更加灵活;对实例值进行条件判断后,更容易使装饰器通用常见错误。
1. “装饰器”并不是“装饰器模式”
“设计模式”是计算机界的一个著名词汇。如果你是一个Java程序员,你对设计模式一窍不通,那么我打赌你的求职面试过程会相当艰难。
但是在写Python的时候,我们很少会谈到“设计模式”。虽然Python也是一种面向对象的编程语言,但是它的鸭式设计和优秀的动态特性决定了大部分设计模式对我们来说并不是必须的。所以很多Python程序员工作久了可能也没有真正应用过几个设计模式。
然而,“装饰者模式”是一个例外。因为Python的“decorator”和“decorator pattern”名字完全相同,所以我不止一次听人把它们当成一回事,以为用“decorator”就是在练“decorator pattern”。但实际上,它们是两个完全不同的东西。
“装饰模式”是一种源于“面向对象”的编程技术。它有几个关键组成部分:一个统一的接口定义,若干个遵循该接口的类,类与类之间一层一层的包装。最后,它们共同形成一种“装饰”效果。
但是Python中的“decorator”和“面向对象”没有直接联系,它完全可以只是发生在函数和函数间的把戏。。其实“装饰器”并没有提供一些不可替代的功能,它只是一个“语法糖果”。以下代码使用了装饰器:
@ log _ time @ cache _ result def foo():Pass基本相当于以下:
def foo():passfoo=log _ time(cache _ result(foo))装饰器最大的功劳,在于让我们在某些特定场景时,可以写出更符合直觉、易于阅读的代码.它只是一个“糖”,而不是某个面向对象领域的复杂编程模型。
提示:Python官网上有实现decorator模式的例子。你可以阅读这个例子来更好地了解它。
2. 记得用 functools.wraps() 装饰内层函数
下面是一个简单的装饰器,专门用于打印耗时的函数调用:
import timedef timer(wrapped): Decorator:记录和打印函数需要时间 def decorated (* args,* * kwargs):ST=time . time()ret=wrapped(* args,**kwargs) print(执行时间:{}秒。format(time . time()-ST))ret ret ret decorated @ timer def random _ Sleep(): 随机睡一小会儿 time . Sleep(random . random())timer decorator没有错误,但是用它装饰函数之后,函数原来的签名会被破坏。也就是说,你再也无法正确获取random_sleep函数的名称和文档的内容,所有的签名都将成为被修饰的内部函数的值:
打印(random_sleep。_ _ name _ _)# output decorated print(random _ sleep。_ _ doc _ _) # output None虽然这只是一个小问题,但在某些情况下也可能导致无法检测的bug。幸运的是,标准库functools为它提供了一个解决方案。在定义装饰器时,您只需要用另一个装饰器来装饰内部装饰函数。
这听起来有点复杂,但这只是新的一行代码:
Def timer(wrapped): #将包装函数的实际签名赋给decorated @ func tools . wrapped(wrapped)def decorated(* args,* * kwargs): #.省略了返回装饰。在这个处理之后,计时器装饰器将不会影响它所装饰的函数。
打印(random_sleep。_ _ name _ _)# output random _ sleep print(random _ sleep。_ _ doc _ _) # output 随机睡一小会儿3. 修改外层变量时记得使用 nonlocal
装饰器是函数对象的高级应用。在编写decorators的过程中,你会经常遇到内部函数需要修改外部函数的变量的情况。就像下面这个装饰师:
import func tools def counter(func): Decorator:记录并打印调用次数 count=0 @ func tools . wraps(func)def decorated(* args,* * kwargs):# cumulative count=1 print(f count:{ count } )return func(* args,* * kwargs)return decorated @ counter def foo():passfoo()为了统计函数调用的次数,我们需要在修饰函数内部修改外部函数定义的count变量的值。但是,上面的代码是有问题的,解释器在执行时会报错:
Traceback(最近一次调用last):文件“counter.py”,第22行,在模块foo()文件“counter.py”,第11行,在修饰count=1中无界localerror:在赋值前引用了局部变量“count”此错误是由counter和修饰函数的嵌套范围引起的。
当解释器执行到count=1时,它不知道count是在外部作用域中定义的变量。它将count视为局部变量,并在当前范围内查找它。但是最后我没有找到count变量的任何定义,然后抛出了一个错误。
解决这个问题需要通过非本地关键字告诉解释器:“count 变量并不属于当前的 local 作用域,去外面找找吧”,前面的错误就可以解决了。
DEF DEDECLARED (* args,* * kwargs):非本地计数count=1 #.省略.提示:如果想了解更多非本地关键词的历史,可以咨询PEP-3104。
总结
在这篇文章里,我和大家分享一些关于装修工的小技巧和小知识。
一些要点的总结:
所有可调用对象都可以用来实现decorator,混合函数和类,更好地实现decorator wrapt模块非常有用。它可以帮助我们用更简单的代码编写复杂的装饰器。“装饰者”只是语法糖,并不是“装饰者模式”。装饰者会改变函数的原始签名。当内部函数修改外部函数的变量时,需要functools.wraps使用nonlocal关键字。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。