标准库中的装饰器
Python内置了三个用于装饰方法的函数: property、classmethod和staticmethod。
另一个常见的装饰器是functools.wraps,它的作用是协助构建行为良好的装饰器。
使用functools.lru_cache做备忘
functools.lru_cache是非常实用的装饰器,它实现了备忘(memorization)功能。这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。
其中LRU三个字母是“Least Recently Used”的缩写,表明缓存不会无限制增长,一段时间不用的缓存条目会被扔掉。
#生成第n个斐波纳契数,递归方式非常耗时,使用缓存实现,速度更快
import functools
from clockdeco import clock
@functools.lru_cache() # ➊
@clock # ➋
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
if __name__=='__main__':
print(fibonacci(6))
➊ 注意,必须像常规函数那样调用lru_cache。这一行中有一对括号:@functools.lru_cache()。这么做的原因是,lru_cache可以接受配置参数,稍后说明。
➋ 这里叠放了装饰器:@lru_cache()应用到@clock返回的函数上。
运行得到的结果如下,这样一来,执行时间减半了,而且n的每个值只调用一次函数
[0.00000119s] fibonacci(0) -> 0
[0.00000119s] fibonacci(1) -> 1
[0.00010800s] fibonacci(2) -> 1
[0.00000787s] fibonacci(3) -> 2
[0.00016093s] fibonacci(4) -> 3
[0.00001216s] fibonacci(5) -> 5
[0.00025296s] fibonacci(6) -> 8
单分派泛函数
#生成HTML的htmlize函数,调整了几种对象的输出
>>> htmlize({1, 2, 3}) #➊
'<pre>{1, 2, 3}</pre>'
>>> htmlize(abs)
'<pre><built-in function abs></pre>'
>>> htmlize('Heimlich & Co.\n- a game') #➋
'<p>Heimlich & Co.<br>\n- a game</p>'
>>> htmlize(42) #➌
'<pre>42 (0x2a)</pre>'
>>> print(htmlize(['alpha', 66, {3, 2, 1}])) #➍
<ul>
<li><p>alpha</p></li>
<li><pre>66 (0x42)</pre></li>
<li><pre>{1, 2, 3}</pre></li>
</ul>
➊ 默认情况下,在<pre>中显示HTML转义后的对象字符串表示形式。
➋ 为str对象显示的也是HTML转义后的字符串表示形式,不过放在<p></p>中,而且使用<br>表示换行。
➌ int显示为十进制和十六进制两种形式,放在<pre>中。
➍ 各个列表项目根据各自的类型格式化,整个列表则渲染成HTML列表。
因为Python不支持重载方法或函数,所以我们不能使用不同的签名定义htmlize的变体,也无法使用不同的方式处理不同的数据类型。
在Python中,一种常见的做法是htmlize变成一个分派函数,使用一串if/elif/elif,调用专门的函数,如htmlize_str、htmlize_int,等等。这样不便于模块的用户扩展,还显得笨拙:时间一长,分派函数htmlize会变得很大,而且它与各个专门函数之间的耦合也很紧密。
Python 3.4新增的functools.singledispatch装饰器可以把整体方案拆分成多个模块,甚至可以为你无法修改的类提供专门的函数。使用@singledispatch装饰的普通函数会变成泛函数(generic function):根据第一个参数的类型,以不同方式执行相同操作的一组函数。
singledispatch创建一个自定义的htmlize.register装饰器,把多个函数绑
在一起组成一个泛函数
from functools import singledispatch
from collections import abc
import numbers
import html
@singledispatch #➊
def htmlize(obj):
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)
@htmlize.register(str) #➋
def _(text): #➌
content = html.escape(text).replace('\n', '<br>\n')
return '<p>{0}</p>'.format(content)
@htmlize.register(numbers.Integral) #➍
def _(n):
return '<pre>{0} (0x{0:x})</pre>'.format(n)
@htmlize.register(tuple) #➎
@htmlize.register(abc.MutableSequence)
def _(seq):
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>' + inner + '</li>\n</ul>'
➊ @singledispatch标记处理object类型的基函数。
➋ 各个专门函数使用@«base_function».register(«type»)装饰。
➌ 专门函数的名称无关紧要;_是个不错的选择,简单明了。
➍ 为每个需要特殊处理的类型注册一个函数。numbers.Integral是int的虚拟超类。
➎ 可以叠放多个register装饰器,让同一个函数支持不同类型。