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

[经验分享] python decorator的理解

[复制链接]

尚未签到

发表于 2017-4-27 06:21:00 | 显示全部楼层 |阅读模式
简介
  每次我们看到decorator的时候,如果有java背景的人会潜意识的想到decorator pattern。的确,decorator pattern是一种将一些功能叠加到一个类上的一种手法。在java里我们需要费一些手脚才能把这些东西给叠加起来。那么在python里面呢?decorator的作用和意义仅仅是在于这个pattern么?我们可以来详细的看看。

python decorator的定义和应用
  在python里定义decorator主要有两种方式,一种是基于方法的方式,一种是基于类定义的方式,他们定义的形式稍微有一些不同,不过都基于同样的思想。在看decorator的具体定义之前,我们先看看python里对待方法的处理方式。

函数的定义和传递
  我们来看下面的代码:

def outer():
def inner():
print("Inside inner function.")
return inner
  这部分代码比较简单,就是在一个outer的方法里定义了一个inner的方法。然后比较奇怪的是,outer方法返回的是inner方法。这就是一个看来很难理解的地方。在python里,方法/函数都可以作为一个类似于普通对象参数进行传递。这是很多函数式编程语言里常用的思想,在后续的文章里还会进一步讨论。所以这里outer方法里定义了inner方法,然后它返回了inner方法。那么我们该怎么使用它们呢?
  这是使用这些代码的典型交互结果:

>>> from outer import outer
>>> foo = outer()
>>> foo()
Inside inner function.
  这里,我们将outer()方法的结果作为一个对象赋值给foo。按照前面的理解,实际上就是inner方法。然后我们用foo()这样的方式调用了inner方法。
  前面的这个示例说明了我们可以将函数作为一个对象当作返回结果。实际上,我们也可以将函数作为方法的参数来传递,我们再看另外一个示例:

def outer(func):
def inner():
print("Inner function before func execution.")
return func()
return inner

  这里outer方法传进来了一个func,在inner方法里我们执行了func()。可以说,这个func必须是一个可以执行的对象,比如实现__call__()方法的对象或者就是一个方法。
  我们该怎么使用前面这段代码呢?我们看下面这部分:

>>> def counter():
...     print("counter method execution")
...
>>> foo = outer(counter)
>>> foo()
Inner function before func execution.
counter method execution

  这里我们定义了一个counter方法。然后将counter方法作为参数传递给outer方法。然后我们可以将outer方法调用的结果再赋值给foo,然后再把foo当作一个方法来调用。
  嗯,经过这一通折腾,我们就知道了在python里,可以定义一个函数,然后将它当成一个普通的对象参数来传递或者返回。这个参数在使用的时候就和调用普通方法一样。这种手法看起来比较怪,不过还是好理解。这些东西和decorator有什么关系呢?其实有了这一步,我们基于函数的方式来定义decorator就只差一点了。

基于函数的定义
  在前面那一部分,我们后面定义的示例里将一个函数传递到一个函数里,然后在inner()方法里做了一些包装和其他操作。这个过程不就是decorator所期望达到的效果吗?确实,在将函数作为参数这样传递的时候,我们无意间已经做到这个包装效果了。不过在python里,有一个更加典型的用法。我们还是以前面的示例为基础,假定我们已经定义好了outer方法和inner方法,我们将counter方法定义写成如下的方式:

>>> @outer
... def counter():
...     print("counter method execution")
  然后我们再用这样的方式来执行counter方法:

>>> counter()
Inner function before func execution.
counter method execution
  很奇怪,居然达到了一个同样的效果。实际上,我们的代码和前面的区别就在于加了一个@outer的修饰在方法定义上。问题的根源也就在于这里。这个@outer的修饰就相当于一个语法糖,我们执行counter()方法的时候实际上相当于变成了outer(counter)()。
  我们来看基于函数的方式来定义的decorator。他们几乎有一个最外层的函数,这个函数相当于一个中介一样,它本身一般不做什么,通常只是用来返回内部的函数。当然,在一些特殊的情况下它可能会传递一些参数。然后内部的那个方法可以访问通过外部方法传递进来的参数,这些参数里通常就包含有需要包装的函数或者对象了。在这个内部方法里除了调用传进来的函数参数,我们也可以做一些自己定义的东西。这样,我们定义的函数式decorator就是这么回事了。

基于类的定义
  看完前面基于函数的定义,我们再来看看基于类的定义。也许很多习惯了定义类的思路会更喜欢这种选择。不过确实,在实现上,基于类定义的方式显得更加直观和强大一些。我们先以前面的示例为基础,假设我们要定义一个outer的decorator类,那么我们该怎么做呢?

class outer(object):
def __init__(self, func):
self.func = func
def __call__(self):
print("Inner function before func execution.")
return self.func()

  这里我们将outer定义成一个类,然后我们需要传递的函数作为一个参数传递给__init__方法。然后原来inner方法里实现包装操作的方法我们放到__call__方法里。这样,我们按照原来的方式来使用这部分代码,执行结果如下:

>>> from outer import outer
>>> @outer
... def counter():
...     print("counter method execution")
...
>>> counter()
Inner function before func execution.
counter method execution

  看了基于类方式定义decorator的方式。那么这种方式和基于函数定义的方式比起来,有什么好处呢?其实好处主要有这么些个。一个是这里将需要包装的函数作为参数传递进来,在传递过来时通过__init__方法来专门负责封装。而__call__方法来专门负责增加自定义的特性。这样两者的分离使得管理更加清晰。而且如果我们需要增加额外的属性也比较好管理。

和decorator pattern以及aop的关系
  看了前面的示例,我们会发现python里的decorator其实可以做到好几个事。一个是decorator pattern。在我的这篇文章里讨论了decorator pattern。而decorator pattern我们知道,需要对一个对象的某些属性增加一些方法或者属性,所以我们要构造一个可以不断叠加属性进去的机制。而在decorator里,我们可以针对每个需要叠加的特性,定义好专门的decorator,然后再将需要被叠加的对象传递进来就可以了。和java的实现比起来显得更加简单直接。
  decorator和java里的aop看起来也有很强的关系。在前面的示例里,我们可以在某个方法被调用前或者调用后执行某些代码。只要我们将这个decorator修饰加到目的对象上就可以了。如果在java里实现类似的功能呢?我们发现本身语言的支持还是比较困难的,比如说我们要实现一个annotation,然后还要用反射去处理。或者用专门的工具就为了操作byte code。所以说在java里比较困难的地方在python这里反而很简单了。

总结
  Decorator在python里是一个基本的语言特性,使用它能够达到decorator pattern或者aop等效果。从更深的层次来说,它无非就是一个函数对另外一个函数对象的封装和传递。感觉和函数式编程语言里的curry化以及高阶函数有比较深的关系。后面有机会针对这两个点再深入的讨论讨论。

参考材料
  http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/
  http://www.artima.com/weblogs/viewpost.jsp?thread=240808
  https://wiki.python.org/moin/PythonDecoratorLibrary
  http://www.jeffknupp.com/blog/2013/11/29/improve-your-python-decorators-explained/
  http://stackoverflow.com/questions/20945366/python-decorators

运维网声明 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-369641-1-1.html 上篇帖子: Python的模块、类、对象 下篇帖子: Dave Python 练习十四 -- 模块
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

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

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

扫描微信二维码查看详情

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


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


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


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



合作伙伴: 青云cloud

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