设为首页 收藏本站
查看: 963|回复: 0

[经验分享] Python中的装饰器(decorator)

[复制链接]

尚未签到

发表于 2015-4-20 06:20:28 | 显示全部楼层 |阅读模式
  想理解Python的decorator首先要知道在Python中函数也是一个对象,所以你可以


  • 将函数复制给变量
  • 将函数当做参数
  • 返回一个函数
  函数在Python中给变量的用法一样也是一等公民,也就是高阶函数(High Order Function)。所有的魔法都是由此而来。

1,起源
  我们想在函数login中输出调试信息,我们可以这样做



def login():
print('in login')
def printdebug(func):
print('enter the login')
func()
print('exit the login')
printdebug(login)
  这个方法讨厌的是每次调用login是,都通过printdebug来调用,但毕竟这是可行的。

2,让代码变得优美一点
  既然函数可以作为返回值,可以赋值给变量,我们可以让代码优美一点。



def login():
print('in login')
def printdebug(func):
def __decorator():
print('enter the login')
func()
print('exit the login')
return __decorator  #function as return value
debug_login = printdebug(login)  #function assign to variable
debug_login()  #execute the returned function
  这样我们每次只要调用debug_login就可以了,这个名字更符合直觉。我们将原先的两个函数printdebug和login绑定到一起,成为debug_login。这种耦合叫内聚:-)。

3,让代码再优美一点
  printdebug和login是通过debug_login = printdebug(login)这一句来结合的,这一句似乎也是多余的,能不能在定义login是加个标注,从而将printdebug和login结合起来?
  上面的代码从语句组织的角度来讲很难再优美了,Python的解决方案是提供一个语法糖(Syntax Sugar),用一个@符号来结合它们。



def printdebug(func):
def __decorator():
print('enter the login')
func()
print('exit the login')
return __decorator  
@printdebug  #combine the printdebug and login
def login():
print('in login')
login()  #make the calling point more intuitive
  可以看出decorator就是一个:使用函数作参数并且返回函数的函数。通过改进我们可以得到:


  • 更简短的代码,将结合点放在函数定义时
  • 不改变原函数的函数名
  在Python解释器发现login调用时,他会将login转换为printdebug(login)()。也就是说真正执行的是__decorator这个函数。

4,加上参数
  1,login函数带参数
  login函数可能有参数,比如login的时候传人user的信息。也就是说,我们要这样调用login:



login(user)
  Python会将login的参数直接传给__decorator这个函数。我们可以直接在__decorator中使用user变量:



def printdebug(func):
def __decorator(user):    #add parameter receive the user information
print('enter the login')
func(user)  #pass user to login
print('exit the login')
return __decorator  
@printdebug  
def login(user):
print('in login:' + user)
login('jatsz')  #arguments:jatsz
  我们来解释一下login(‘jatsz’)的调用过程:

  [decorated] login(‘jatsz’)   =>   printdebug(login)(‘jatsz’)  =>  __decorator(‘jatsz’)  =>  [real] login(‘jatsz’)

  2,装饰器本身有参数
  我们在定义decorator时,也可以带入参数,比如我们这样使用decorator,我们传入一个参数来指定debug level。



@printdebug(level=5)
def login
pass
  为了给接收decorator传来的参数,我们在原本的decorator上在包装一个函数来接收参数:



def printdebug_level(level):  #add wrapper to recevie decorator's parameter
def printdebug(func):
def __decorator(user):   
print('enter the login, and debug level is: ' + str(level)) #print debug level
func(user)  
print('exit the login')
return __decorator  
return printdebug    #return original decorator
@printdebug_level(level=5)   #decorator's parameter, debug level set to 5
def login(user):
print('in login:' + user)
login('jatsz')  
  我们再来解释一下login(‘jatsz’)整个调用过程:

  [decorated]login(‘jatsz’) => printdebug_level(5) => printdebug[with closure value 5](login)(‘jatsz’) => __decorator(‘jatsz’)[use value 5]  => [real]login(‘jatsz’)

  

5,装饰有返回值的函数
  有时候login会有返回值,比如返回message来表明login是否成功。



login_result = login(‘jatsz’)
  我们需要将返回值在decorator和调用函数间传递:



def printdebug(func):
def __decorator(user):   
print('enter the login')
result = func(user)  #recevie the native function call result
print('exit the login')
return result        #return to caller
return __decorator  
@printdebug  
def login(user):
print('in login:' + user)
msg = "success" if user == "jatsz" else "fail"
return msg  #login with a return value
result1 = login('jatsz');
print result1  #print login result
result2 = login('candy');
print result2
  我们解释一下返回值的传递过程:

  ...omit for brief…[real][msg from login(‘jatsz’) => [result from]__decorator => [assign to] result1


6,应用多个装饰器
  我们可以对一个函数应用多个装饰器,这时我们需要留心的是应用装饰器的顺序对结果会产生。影响比如:



def printdebug(func):
def __decorator():   
print('enter the login')
func()
print('exit the login')
return __decorator  
def others(func):    #define a other decorator
def __decorator():
print '***other decorator***'
func()
return __decorator
@others         #apply two of decorator
@printdebug
def login():
print('in login:')
@printdebug    #switch decorator order
@others
def logout():
print('in logout:')
login()
print('---------------------------')
logout()
  我们定义了另一个装饰器others,然后我们对login函数和logout函数分别应用这两个装饰器。应用方式很简单,在函数定义是直接用两个@@就可以了。我们看一下上面代码的输出:



$ python deoc.py
***other decorator***
enter the login
in login:
exit the login
---------------------------
enter the login
***other decorator***
in logout:
exit the login
  我们看到两个装饰器都已经成功应用上去了,不过输出却不相同。造成这个输出不同的原因是我们应用装饰器的顺序不同。回头看看我们login的定义,我们是先应用others,然后才是printdebug。而logout函数真好相反,发生了什么?如果你仔细看logout函数的输出结果,可以看到装饰器的递归。从输出可以看出:logout函数先应用printdebug,打印出“enter the login”。printdebug的__decorator调用中间应用了others的__decorator,打印出“***other decorator***”。其实在逻辑上我们可以将logout函数应用装饰器的过程这样看(伪代码):



@printdebug    #switch decorator order
(
@others
(
def logout():
print('in logout:')
)
)
  我们解释一下整个递归应用decorator的过程:

  [printdebug decorated]logout() =>
  printdebug.__decorator[call [others decorated]logout() ] =>
  printdebug.__decorator.other.__decorator[call real logout]

  

7,灵活运用
  什么情况下装饰器不适用?装饰器不能对函数的一部分应用,只能作用于整个函数。
  login函数是一个整体,当我们想对部分函数应用装饰器时,装饰器变的无从下手。比如我们想对下面这行语句应用装饰器:



msg = "success" if user == "jatsz" else "fail"
  怎么办?
  一个变通的办法是“提取函数”,我们将这行语句提取成函数,然后对提取出来的函数应用装饰器:



def printdebug(func):
def __decorator(user):   
print('enter the login')
result = func(user)
print('exit the login')
return result      
return __decorator  
def login(user):
print('in login:' + user)
msg = validate(user)  #exact to a method
return msg  
@printdebug  #apply the decorator for exacted method
def validate(user):
msg = "success" if user == "jatsz" else "fail"
return msg
result1 = login('jatsz');
print result1  
  
  来个更加真实的应用,有时候validate是个耗时的过程。为了提高应用的性能,我们会将validate的结果cache一段时间(30 seconds),借助decorator和上面的方法,我们可以这样实现:



import time
dictcache = {}
def cache(func):
def __decorator(user):   
now = time.time()
if (user in dictcache):
result,cache_time = dictcache[user]
if (now - cache_time) > 30:  #cache expired
result = func(user)
dictcache[user] = (result, now)  #cache the result by user
else:
print('cache hits')
else:
result = func(user)
dictcache[user] = (result, now)
return result      
return __decorator  
def login(user):
print('in login:' + user)
msg = validate(user)  
return msg  
@cache  #apply the cache for this slow validation
def validate(user):
time.sleep(5)  #simulate 10 second block
msg = "success" if user == "jatsz" else "fail"
return msg
result1 = login('jatsz'); print result1  
result2 = login('jatsz'); print result2    #this login will return immediately by hit the cache
result3 = login('candy'); print result3
  

  Reference:
  http://stackoverflow.com/questions/739654/understanding-python-decorators      --Understanding Python decorators
  http://www.iyunv.com/rhcad/archive/2011/12/21/2295507.html   --Python装饰器学习(九步入门)
  http://www.python.org/dev/peps/pep-0318/   --PEP 318 -- Decorators for Functions and Methods

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-58579-1-1.html 上篇帖子: 从C#到Python —— 2 运算符、表达式和流程控制 下篇帖子: 【循序渐进学Python】11.常用标准库
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表