当初看 Dive into Python 的时候就听过装饰器,不过知道是个语法糖,感觉上没什么用,就没仔细了解,这几天看一段代码用到了装饰器,只好再研究下吧。

为什么使用装饰器?

AstralWind 在他的博客里这么说,“装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。”

打个比方,我是个有怪癖的人,在调用任何函数的时候总希望先输出函数的name和doc,因为我常常反复传递函数,所以最好把这个函数的引用计数也显示下?要么把函数运行时间也显示出来吧!

如果不用装饰器,我们可能在写函数的时候把name、doc什么的都print出来,这样有些麻烦,写多少个函数就得copy多少次,利用Python万物皆对象的特性,我们不妨写个函数来做这件事情:

不过这样在调用函数的时候总得先把函数传给PrintDetail(),既不美观也不方便,那么是时候让装饰器登场了:

这里我们定义了装饰器,不过,等等,我运行完发现怎么我还没调用func,他就运行了?
上面这个装饰器是不合格的,如果你尝试去掉用的话,甚至会发现装饰器只会调用一次。
这是因为我们还没弄清楚究竟什么时装饰器,我先提供一个正确的装饰器:

为什么要提供_PrintDetail,而且还要把它返回?
为什么现在看起来就不会没调用自动运行?
为什么现在多次运行都可以自动调用装饰器了?

虽然没有读过Python关于这部分的源码,但是大概可以推测出Python涉及到装饰器时怎么工作的:

1. 将目标函数func作为参数传给装饰器PrintDetail。
2. 运行装饰器,返回经过装饰后的函数,在这里是_PrintDetail。
3. 将func关联到_PrintDetail,以后对该名字(如果你对这里为什么用名字而不用函数有疑问,建议看下雨痕的Python学习笔记)。

所以对于decorator.1.py,装饰器运行完,显示了name、doc,返回的还是原函数,以后调用的时候还是原函数。
而对于decorator.2.py,装饰器运行完,返回的就是经过装饰的函数了,以后在调用func的时候,装饰器也就自动运行了。
你可以在PrintDetail里添加点调试语句,测试测试。比如在def deco(func)和def _deco(func)之间加个print ‘here’。

上面的演示总是装饰一个函数,比较简单,如果多个函数呢?而且多个函数所需要的参数列表也不同?
在定义形参的时候,我们优势可以见到像*args、**kwargs。前者*params的作用是收集其余的位置参数,返回元组,**params则收集其余的关键字参数,返回字典。一般来说元组的计算速度更快,我在这里拿元组做个演示(这里我是直接把函数打印出来,所以没有return,你可以自己实验下return):

话说,又有同学问了,我忽然发现运行print my_power.__name__的时候,出现的居然是_PrintDetail!我可不想这样啊,肿么办?答曰:先from functools import wraps,然后在def _PrintDetail(*args):前加一句@wraps(func)即可。

其实,装饰器不只可以装饰函数的!

甚至装饰器都不一定得是个函数,甚至装饰器可以接受参数,甚至装饰器可以串接(就是对一个函数/类连着用N个装饰器)!
但是这些用的比较少,我就不细写了,有兴趣的童鞋可以看着里:Decorators and Functional Python或者这里由youngsterxyf童鞋翻的译文:装饰器与函数式Python

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

你可以管理本篇文章的订阅。

Post Navigation