浅谈python中的@以及@在tensorflow中的作用说明
虽然用python用了很久了,但是主要还是写一些模型或者算子,对于python中的高级特性用的不多,但是时常阅读大牛的代码或者框架源码,其中python特性应用的非常流畅,所以今天决定与python中的装饰器@,做个了断!!
Python中的@:
援引廖雪峰老师对装饰器的解释以及一些自己对装饰器的理解:
python中在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。@是装饰器的语法。装饰器是在函数调用之上的修饰,这些修饰仅是当声明一个函数或者方法的时候,才会应用的额外调用。我们可以用装饰器来增加计时逻辑来检测性能,或者引入日志等等。
函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
>>>defnow(): ...print('2015-3-25') ... >>>f=now >>>f() 2015-3-25
函数对象有一个__name__属性,可以拿到函数的名字:
>>>now.__name__ 'now' >>>f.__name__ 'now'
现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义。本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
deflog(func): defwrapper(*args,**kw): print('call%s():'%func.__name__) returnfunc(*args,**kw) returnwrapper
观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
@log defnow(): print('2015-3-25')
调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:
>>>now() callnow(): 2015-3-25
把@log放到now()函数的定义处,相当于执行了语句:
now=log(now)
由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args,**kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
deflog(text): defdecorator(func): defwrapper(*args,**kw): print('%s%s():'%(text,func.__name__)) returnfunc(*args,**kw) returnwrapper returndecorator
这个3层嵌套的decorator用法如下:
@log('execute') defnow(): print('2015-3-25')
执行结果如下:
>>>now() executenow(): 2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>>now=log('execute')(now)
我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper':
>>>now.__name__ 'wrapper'
因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__=func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:
importfunctools deflog(func): @functools.wraps(func) defwrapper(*args,**kw): print('call%s():'%func.__name__) returnfunc(*args,**kw) returnwrapper
或者针对带参数的decorator:
importfunctools deflog(text): defdecorator(func): @functools.wraps(func) defwrapper(*args,**kw): print('%s%s():'%(text,func.__name__)) returnfunc(*args,**kw) returnwrapper returndecorator
importfunctools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。
python中常见的@:
@property:对于类的方法,装饰器一样起作用,Python内置的@property装饰器就是负责把一个方法变成属性调用的.广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。
classStudent(object): @property defscore(self): returnself._score @score.setter defscore(self,value): ifnotisinstance(value,int): raiseValueError('scoremustbeaninteger!') ifvalue<0orvalue>100: raiseValueError('scoremustbetween0~100!') self._score=value
@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
>>>s=Student() >>>s.score=60#OK,实际转化为s.set_score(60) >>>s.score#OK,实际转化为s.get_score() 60 >>>s.score=9999 Traceback(mostrecentcalllast): ... ValueError:scoremustbetween0~100!
注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。
@staticmethod,@classmethod:@staticmethod返回的是一个staticmethod类对象,而@classmethod返回的是一个classmethod类对象。他们都是调用的是各自的__init__()构造函数。
当然应用装饰器不当也会带来一些问题:
1、位置错误的代码
让我们直接看示例代码。
defhtml_tags(tag_name): print'beginouterfunction.' defwrapper_(func): print"beginofinnerwrapperfunction." defwrapper(*args,**kwargs): content=func(*args,**kwargs) print"<{tag}>{content}{tag}>".format(tag=tag_name,content=content) print'endofinnerwrapperfunction.' returnwrapper print'endofouterfunction' returnwrapper_ @html_tags('b') defhello(name='Toby'): return'Hello{}!'.format(name) hello() hello()
在装饰器中我在各个可能的位置都加上了print语句,用于记录被调用的情况。你知道他们最后打印出来的顺序吗?如果你心里没底,那么最好不要在装饰器函数之外添加逻辑功能,否则这个装饰器就不受你控制了。以下是输出结果:
beginouterfunction. endofouterfunction beginofinnerwrapperfunction. endofinnerwrapperfunction. HelloToby! HelloToby!
2、错误的函数签名和文档
装饰器装饰过的函数看上去名字没变,其实已经变了。
deflogging(func): defwrapper(*args,**kwargs): """printlogbeforeafunction.""" print"[DEBUG]{}:enter{}()".format(datetime.now(),func.__name__) returnfunc(*args,**kwargs) returnwrapper @logging defsay(something): """saysomething""" print"say{}!".format(something) printsay.__name__#wrapper
为什么会这样呢?想想装饰器的语法@代替的东西就明白了。@等同于这样的写法。
say=logging(say)
logging其实返回的函数名字刚好是wrapper,那么上面的这个语句刚好就是把这个结果赋值给say,say的__name__自然也就是wrapper了,不仅仅是name,其他属性也都是来自wrapper,比如doc,source等等。
使用标准库里的functools.wraps,可以基本解决这个问题。
fromfunctoolsimportwraps deflogging(func): @wraps(func) defwrapper(*args,**kwargs): """printlogbeforeafunction.""" print"[DEBUG]{}:enter{}()".format(datetime.now(),func.__name__) returnfunc(*args,**kwargs) returnwrapper @logging defsay(something): """saysomething""" print"say{}!".format(something) printsay.__name__#say printsay.__doc__#saysomething
看上去不错!主要问题解决了,但其实还不太完美。因为函数的签名和源码还是拿不到的。
importinspect printinspect.getargspec(say)#failed printinspect.getsource(say)#failed
如果要彻底解决这个问题可以借用第三方包,比如wrapt。
3、不能装饰@staticmethod或者@classmethod
当你想把装饰器用在一个静态方法或者类方法时,不好意思,报错了。
classCar(object): def__init__(self,model): self.model=model @logging#装饰实例方法,OK defrun(self): print"{}isrunning!".format(self.model) @logging#装饰静态方法,Failed @staticmethod defcheck_model_for(obj): ifisinstance(obj,Car): print"Themodelofyourcaris{}".format(obj.model) else: print"{}isnotacar!".format(obj) """ Traceback(mostrecentcalllast): ... File"example_4.py",line10,inlogging @wraps(func) File"C:\Python27\lib\functools.py",line33,inupdate_wrapper setattr(wrapper,attr,getattr(wrapped,attr)) AttributeError:'staticmethod'objecthasnoattribute'__module__' """
前面已经解释了@staticmethod这个装饰器,其实它返回的并不是一个callable对象,而是一个staticmethod对象,那么它是不符合装饰器要求的(比如传入一个callable对象),你自然不能在它之上再加别的装饰器。要解决这个问题很简单,只要把你的装饰器放在@staticmethod之前就好了,因为你的装饰器返回的还是一个正常的函数,然后再加上一个@staticmethod是不会出问题的。
classCar(object): def__init__(self,model): self.model=model @staticmethod @logging#在@staticmethod之前装饰,OK defcheck_model_for(obj): pass
如何优化你的装饰器:
嵌套的装饰函数不太直观,我们可以使用第三方包类改进这样的情况,让装饰器函数可读性更好。
decorator.py
decorator.py是一个非常简单的装饰器加强包。你可以很直观的先定义包装函数wrapper(),再使用decorate(func,wrapper)方法就可以完成一个装饰器。
fromdecoratorimportdecorate defwrapper(func,*args,**kwargs): """printlogbeforeafunction.""" print"[DEBUG]{}:enter{}()".format(datetime.now(),func.__name__) returnfunc(*args,**kwargs) deflogging(func): returndecorate(func,wrapper)#用wrapper装饰func
你也可以使用它自带的@decorator装饰器来完成你的装饰器。
fromdecoratorimportdecorator @decorator deflogging(func,*args,**kwargs): print"[DEBUG]{}:enter{}()".format(datetime.now(),func.__name__) returnfunc(*args,**kwargs)
decorator.py实现的装饰器能完整保留原函数的name,doc和args,唯一有问题的就是inspect.getsource(func)返回的还是装饰器的源代码,你需要改成inspect.getsource(func.__wrapped__)。
wrapt
wrapt是一个功能非常完善的包,用于实现各种你想到或者你没想到的装饰器。使用wrapt实现的装饰器你不需要担心之前inspect中遇到的所有问题,因为它都帮你处理了,甚至inspect.getsource(func)也准确无误。
importwrapt #withoutargumentindecorator @wrapt.decorator deflogging(wrapped,instance,args,kwargs):#instanceismust print"[DEBUG]:enter{}()".format(wrapped.__name__) returnwrapped(*args,**kwargs) @logging defsay(something):pass
使用wrapt你只需要定义一个装饰器函数,但是函数签名是固定的,必须是(wrapped,instance,args,kwargs),注意第二个参数instance是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据instance的值你能够更加灵活的调整你的装饰器。另外,args和kwargs也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。
如果你需要使用wrapt写一个带参数的装饰器,可以这样写。
deflogging(level): @wrapt.decorator defwrapper(wrapped,instance,args,kwargs): print"[{}]:enter{}()".format(level,wrapped.__name__) returnwrapped(*args,**kwargs) returnwrapper @logging(level="INFO") defdo(work):pass
Tensorflow中的@:
tensorflow就巧妙应用的python的装饰器,提高了代码的动态性,也使代码变得精简。
@tf_export的作用是:ProvideswaystoexportsymbolstotheTensorFlowAPI.
@tf_contextlib的作用是:Atf_decorator-awarewrapperfor`contextlib.contextmanager`.
还有@tf_inspect、@tf_should_use等。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持毛票票。如有错误或未考虑完全的地方,望不吝赐教。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。