PHP网站建设的课后笔记,企微管家,ai制作网页,公司做网站会计凭证怎么做Python中的装饰器是你进入Python大门的一道坎#xff0c;不管你跨不跨过去它都在那里。
为什么需要装饰器
我们假设你的程序实现了say_hello()和say_goodbye()两个函数。 def say_hello():print hello!def say_goodbye():print hello! # bug hereif…Python中的装饰器是你进入Python大门的一道坎不管你跨不跨过去它都在那里。
为什么需要装饰器
我们假设你的程序实现了say_hello()和say_goodbye()两个函数。 def say_hello():print hello!def say_goodbye():print hello! # bug hereif __name__ __main__:say_hello()say_goodbye()
但是在实际调用中我们发现程序出错了上面的代码打印了两个hello。经过调试你发现是say_goodbye()出错了。老板要求调用每个方法前都要记录进入函数的名称比如这样 [DEBUG]: Enter say_hello()Hello![DEBUG]: Enter say_goodbye()Goodbye!
好小A是个毕业生他是这样实现的。 def say_hello():print [DEBUG]: enter say_hello()print hello!def say_goodbye():print [DEBUG]: enter say_goodbye()print hello!if __name__ __main__:say_hello()say_goodbye()
很low吧 嗯是的。小B工作有一段时间了他告诉小A可以这样写。 def debug():import inspectcaller_name inspect.stack()[1][3]print [DEBUG]: enter {}().format(caller_name) def say_hello():debug()print hello!def say_goodbye():debug()print goodbye!if __name__ __main__:say_hello()say_goodbye()
是不是好一点那当然但是每个业务函数里都要调用一下debug()函数是不是很难受万一老板说say相关的函数不用debugdo相关的才需要呢
那么装饰器这时候应该登场了。 装饰器本质上是一个Python函数它可以让其他函数在不需要做任何代码变动的前提下增加额外功能装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景比如插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计有了装饰器我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。 概括的讲装饰器的作用就是为已经存在的函数或对象添加额外的功能。
怎么写一个装饰器
在早些时候 (Python Version 2.42004年以前)为一个函数添加额外功能的写法是这样的。 def debug(func):def wrapper():print [DEBUG]: enter {}().format(func.__name__)return func()return wrapperdef say_hello():print hello!say_hello debug(say_hello) # 添加功能并保持原函数名不变
上面的debug函数其实已经是一个装饰器了它对原函数做了包装并返回了另外一个函数额外添加了一些功能。因为这样写实在不太优雅在后面版本的Python中支持了语法糖下面代码等同于早期的写法。 def debug(func):def wrapper():print [DEBUG]: enter {}().format(func.__name__)return func()return wrapperdebugdef say_hello():print hello!
这是最简单的装饰器但是有一个问题如果被装饰的函数需要传入参数那么这个装饰器就坏了。因为返回的函数并不能接受参数你可以指定装饰器函数wrapper接受和原函数一样的参数比如 def debug(func):def wrapper(something): # 指定一毛一样的参数print [DEBUG]: enter {}().format(func.__name__)return func(something)return wrapper # 返回包装过函数debugdef say(something):print hello {}!.format(something)
这样你就解决了一个问题但又多了N个问题。因为函数有千千万你只管你自己的函数别人的函数参数是什么样子鬼知道还好Python提供了可变参数*args和关键字参数**kwargs有了这两个参数装饰器就可以用于任意目标函数了。 def debug(func):def wrapper(*args, **kwargs): # 指定宇宙无敌参数print [DEBUG]: enter {}().format(func.__name__)print Prepare and say...,return func(*args, **kwargs)return wrapper # 返回debugdef say(something):print hello {}!.format(something)
至此你已完全掌握初级的装饰器写法。
高级一点的装饰器
带参数的装饰器和类装饰器属于进阶的内容。在理解这些装饰器之前最好对函数的闭包和装饰器的接口约定有一定了解。(参见Python的闭包)
带参数的装饰器
假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息而且还需指定log的级别那么装饰器就会是这样的。 def logging(level):def wrapper(func):def inner_wrapper(*args, **kwargs):print [{level}]: enter function {func}().format(levellevel,funcfunc.__name__)return func(*args, **kwargs)return inner_wrapperreturn wrapperlogging(levelINFO)def say(something):print say {}!.format(something)# 如果没有使用语法等同于# say logging(levelINFO)(say)logging(levelDEBUG)def do(something):print do {}....format(something)if __name__ __main__:say(hello)do(my work)
是不是有一些晕你可以这么理解当带参数的装饰器被打在某个函数上时比如logging(levelDEBUG)它其实是一个函数会马上被执行只要这个它返回的结果是一个装饰器时那就没问题。细细再体会一下。
基于类实现的装饰器
装饰器函数其实是这样一个接口约束它必须接受一个callable对象作为参数然后返回一个callable对象。在Python中一般callable对象都是函数但也有例外。只要某个对象重载了__call__()方法那么这个对象就是callable的。 class Test():def __call__(self):print call me!t Test()t() # call me
像__call__这样前后都带下划线的方法在Python中被称为内置方法有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。
回到装饰器上的概念上来装饰器要求接受一个callable对象并返回一个callable对象不太严谨详见后文。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()接受一个函数然后重载__call__()并返回一个函数也可以达到装饰器函数的效果。 class logging(object):def __init__(self, func):self.func funcdef __call__(self, *args, **kwargs):print [DEBUG]: enter function {func}().format(funcself.func.__name__)return self.func(*args, **kwargs)loggingdef say(something):print say {}!.format(something)
带参数的类装饰器
如果需要通过类形式实现带参数的装饰器那么会比前面的例子稍微复杂一点。那么在构造函数里接受的就不是一个函数而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接受一个函数并返回一个函数。 class logging(object):def __init__(self, levelINFO):self.level leveldef __call__(self, func): # 接受函数def wrapper(*args, **kwargs):print [{level}]: enter function {func}().format(levelself.level,funcfunc.__name__)func(*args, **kwargs)return wrapper #返回函数logging(levelINFO)def say(something):print say {}!.format(something)
内置的装饰器
内置的装饰器和普通的装饰器原理是一样的只不过返回的不是函数而是类对象所以更难理解一些。
property
在了解这个装饰器前你需要知道在不使用装饰器怎么写一个属性。 def getx(self):return self._xdef setx(self, value):self._x valuedef delx(self):del self._x# create a propertyx property(getx, setx, delx, I am doc for x property)
以上就是一个Python属性的标准写法其实和Java挺像的但是太罗嗦。有了语法糖能达到一样的效果但看起来更简单。 propertydef x(self): ...# 等同于def x(self): ...x property(x)
属性有三个装饰器setter, getter, deleter 都是在property()的基础上做了一些封装因为setter和deleter是property()的第二和第三个参数不能直接套用语法。getter装饰器和不带getter的属性装饰器效果是一样的估计只是为了凑数本身没有任何存在的意义。经过property装饰过的函数返回的不再是一个函数而是一个property对象。 property()property object at 0x10ff07940
staticmethodclassmethod
有了property装饰器的了解这两个装饰器的原理是差不多的。staticmethod返回的是一个staticmethod类对象而classmethod返回的是一个classmethod类对象。他们都是调用的是各自的__init__()构造函数。 class classmethod(object):classmethod(function) - method def __init__(self, function): # for classmethod decoratorpass# ...class staticmethod(object):staticmethod(function) - methoddef __init__(self, function): # for staticmethod decoratorpass# ...
装饰器的语法就等同调用了这两个类的构造函数。 class Foo(object):staticmethoddef bar():pass# 等同于 bar staticmethod(bar)
至此我们上文提到的装饰器接口定义可以更加明确一些装饰器必须接受一个callable对象其实它并不关心你返回什么可以是另外一个callable对象大部分情况也可以是其他类对象比如property。
装饰器里的那些坑
装饰器可以让你代码更加优雅减少重复但也不全是优点也会带来一些问题。
位置错误的代码
让我们直接看示例代码。 def html_tags(tag_name):print begin outer function.def wrapper_(func):print begin of inner wrapper function.def wrapper(*args, **kwargs):content func(*args, **kwargs)print {tag}{content}/{tag}.format(tagtag_name, contentcontent)print end of inner wrapper function.return wrapperprint end of outer functionreturn wrapper_html_tags(b)def hello(nameToby):return Hello {}!.format(name)hello()hello()
在装饰器中我在各个可能的位置都加上了print语句用于记录被调用的情况。你知道他们最后打印出来的顺序吗如果你心里没底那么最好不要在装饰器函数之外添加逻辑功能否则这个装饰器就不受你控制了。以下是输出结果 begin outer function.end of outer functionbegin of inner wrapper function.end of inner wrapper function.bHello Toby!/bbHello Toby!/b
错误的函数签名和文档
装饰器装饰过的函数看上去名字没变其实已经变了。 def logging(func):def wrapper(*args, **kwargs):print log before a function.print [DEBUG] {}: enter {}().format(datetime.now(), func.__name__)return func(*args, **kwargs)return wrapperloggingdef say(something):say somethingprint say {}!.format(something)print say.__name__ # wrapper
为什么会这样呢只要你想想装饰器的语法糖代替的东西就明白了。等同于这样的写法。 say logging(say)
logging其实返回的函数名字刚好是wrapper那么上面的这个语句刚好就是把这个结果赋值给saysay的__name__自然也就是wrapper了不仅仅是name其他属性也都是来自wrapper比如docsource等等。
使用标准库里的functools.wraps可以基本解决这个问题。 from functools import wrapsdef logging(func):wraps(func)def wrapper(*args, **kwargs):print log before a function.print [DEBUG] {}: enter {}().format(datetime.now(), func.__name__)return func(*args, **kwargs)return wrapperloggingdef say(something):say somethingprint say {}!.format(something)print say.__name__ # sayprint say.__doc__ # say something
看上去不错主要问题解决了但其实还不太完美。因为函数的签名和源码还是拿不到的。 import inspectprint inspect.getargspec(say) # failedprint inspect.getsource(say) # failed
如果要彻底解决这个问题可以借用第三方包比如wrapt。后文有介绍。
不能装饰staticmethod 或者 classmethod
当你想把装饰器用在一个静态方法或者类方法时不好意思报错了。 class Car(object):def __init__(self, model):self.model modellogging # 装饰实例方法OKdef run(self):print {} is running!.format(self.model)logging # 装饰静态方法Failedstaticmethoddef check_model_for(obj):if isinstance(obj, Car):print The model of your car is {}.format(obj.model)else:print {} is not a car!.format(obj)Traceback (most recent call last):...File example_4.py, line 10, in loggingwraps(func)File C:\Python27\lib\functools.py, line 33, in update_wrappersetattr(wrapper, attr, getattr(wrapped, attr))AttributeError: staticmethod object has no attribute __module__
前面已经解释了staticmethod这个装饰器其实它返回的并不是一个callable对象而是一个staticmethod对象那么它是不符合装饰器要求的比如传入一个callable对象你自然不能在它之上再加别的装饰器。要解决这个问题很简单只要把你的装饰器放在staticmethod之前就好了因为你的装饰器返回的还是一个正常的函数然后再加上一个staticmethod是不会出问题的。 class Car(object):def __init__(self, model):self.model modelstaticmethodlogging # 在staticmethod之前装饰OKdef check_model_for(obj):pass
如何优化你的装饰器
嵌套的装饰函数不太直观我们可以使用第三方包类改进这样的情况让装饰器函数可读性更好。
decorator.py
decorator.py 是一个非常简单的装饰器加强包。你可以很直观的先定义包装函数wrapper()再使用decorate(func, wrapper)方法就可以完成一个装饰器。 from decorator import decoratedef wrapper(func, *args, **kwargs):print log before a function.print [DEBUG] {}: enter {}().format(datetime.now(), func.__name__)return func(*args, **kwargs)def logging(func):return decorate(func, wrapper) # 用wrapper装饰func
你也可以使用它自带的decorator装饰器来完成你的装饰器。 from decorator import decoratordecoratordef logging(func, *args, **kwargs):print [DEBUG] {}: enter {}().format(datetime.now(), func.__name__)return func(*args, **kwargs)
decorator.py实现的装饰器能完整保留原函数的namedoc和args唯一有问题的就是inspect.getsource(func)返回的还是装饰器的源代码你需要改成inspect.getsource(func.__wrapped__)。
wrapt
wrapt是一个功能非常完善的包用于实现各种你想到或者你没想到的装饰器。使用wrapt实现的装饰器你不需要担心之前inspect中遇到的所有问题因为它都帮你处理了甚至inspect.getsource(func)也准确无误。 import wrapt# without argument in decoratorwrapt.decoratordef logging(wrapped, instance, args, kwargs): # instance is mustprint [DEBUG]: enter {}().format(wrapped.__name__)return wrapped(*args, **kwargs)loggingdef say(something): pass
使用wrapt你只需要定义一个装饰器函数但是函数签名是固定的必须是(wrapped, instance, args, kwargs)注意第二个参数instance是必须的就算你不用它。当装饰器装饰在不同位置时它将得到不同的值比如装饰在类实例方法时你可以拿到这个类实例。根据instance的值你能够更加灵活的调整你的装饰器。另外args和kwargs也是固定的注意前面没有星号。在装饰器内部调用原函数时才带星号。
如果你需要使用wrapt写一个带参数的装饰器可以这样写。 def logging(level):wrapt.decoratordef wrapper(wrapped, instance, args, kwargs):print [{}]: enter {}().format(level, wrapped.__name__)return wrapped(*args, **kwargs)return wrapperlogging(levelINFO)def do(work): pass
关于wrapt的使用建议查阅官方文档在此不在赘述。
Getting Started — wrapt 1.13.0rc2 documentation
小结
Python的装饰器和Java的注解Annotation并不是同一回事和C#中的特性Attribute也不一样完全是两个概念。
装饰器的理念是对原函数、对象的加强相当于重新封装所以一般装饰器函数都被命名为wrapper()意义在于包装。函数只有在被调用时才会发挥其作用。比如logging装饰器可以在函数执行时额外输出日志cache装饰过的函数可以缓存计算结果等等。
而注解和特性则是对目标函数或对象添加一些属性相当于将其分类。这些属性可以通过反射拿到在程序运行时对不同的特性函数或对象加以干预。比如带有Setup的函数就当成准备步骤执行或者找到所有带有TestMethod的函数依次执行等等。
至此我所了解的装饰器已经讲完但是还有一些内容没有提到比如装饰类的装饰器。有机会再补充。谢谢观看。