无论何时,在对一个对象添加额外功能时,都有以下可选方法:
- 如果合理,直接将功能添加到对象所属的类;
- 使用组合;
- 使用继承。
设计模式为我们提供了第四种可选方法,以支持动态地(运行时)扩展一个对象的功能,这种方法就是修饰器(Decorator)。
- 修饰器模式能够以透明的方式(不影响其他对象)动态地将功能添加到一个对象中。
- 在很多编程语言里,使用继承来实现修饰器模式。在Python中,可以使用内置的修饰器特性,一个Python修饰器就是对Python语法的特定改变,用于扩展一个类、方法或函数的行为,而无需使用继承。
- 从实现的角度来说,Python修饰器是一个可调用对象(函数、方法、类),接受一个函数对象fin作为输入,并返回另一个函数对象fout。
应用案例
应用Python修饰器模式,解决实现斐波那契数列的性能问题。
首先来看看朴素的实现方法:
def fibonacci(n):
assert(n >= 0), 'n must be >= 0!'
return n if n in (0, 1) else fibonacci(n-1) + fibonacci(n-2)
if __name__ == '__main__':
from timeit import Timer
t = Timer('fibonacci(10)', 'from __main__ import fibonacci')
print(t.timeit())
----------
输出:
25.401731605998066
执行结果可知,朴素方法速度有多慢!计算第10个斐波那契数就要25秒。
使用memoization提速递归函数
known = {0:0, 1:1}
def fibonacci(n):
assert(n >= 0), 'n must be >= 0!'
if n in known:
return known[n]
res = fibonacci(n-1) + fibonacci(n-2)
known[n] = res
return res
if __name__ == '__main__':
from timeit import Timer
t = Timer('fibonacci(10)', 'from __main__ import fibonacci')
----------
输出:
0.1905040599995118
在上述代码中,我们使用了一个dict来缓存斐波那契数列中已经计算好的数值。同样计算第10个斐波那契数,可以看到性能得到了极大地提升。
但该方法有一些问题。虽然性能得到提升,但代码也没有不使用memoization时那样简洁。如何能在保持递归函数与朴素版本一样简单,在性能上又能与使用memoization的函数相近呢?解决方法就是使用修饰器模式。
修饰器模式
def memoize(fn):
know = dict()
def memoizer(*args):
if args not in know:
know[args] = fn(*args)
return know[args]
return memoizer
@memoize
def fibonacci(n):
assert(n >= 0),'n must be >= 0!'
return n if n in (0,1) else fibonacci(n-1)+fibonacci(n-2)
if __name__ == '__main__':
from timeit import Timer
t = Timer('fibonacci(100)', 'from __main__ import fibonacci')
print(t.timeit())
----------
输出:
0.3000087060026999
首先创建了一个memoize()
函数,这个修饰器接受一个需要使用memoization的函数fn作为输入,使用一个字典作为缓存。修饰使用@name
语法,其中name是指我们想要使用的修饰器的名称。这其实只不过是一个简化修饰器使用的语法糖。
从输出可以看出:计算第100个斐波那契数只需要约0.3秒。这一方案同时具备可读的代码和可接受的性能。
另外,再看一个例子:给定一个混合大小写,并带有标点符号的字符串,使用装饰器进行操作:
- 清除标点符号
- 转化为小写
- 最后形成词的列表
from string import punctuation
def pipeline_wrapper(func):
def to_lower(x):
return x.lower()
def remove_punc(x):
for i in punctuation:
x = x.replace(i, '')
return x
def wrapper(*args, **kwargs):
x = to_lower(*args, **kwargs)
x = remove_punc(x)
return func(x)
return wrapper
@pipeline_wrapper
def func(intext):
return intext.split()
if __name__ == '__main__':
intest = 'stringt. With. Punctuation?'
print(func(intest))
----------
输出:
['stringt', 'with', 'punctuation']
所以,修饰器模式可以扩展一个对象的行为,无需使用继承,非常方便。Python进一步扩展了修饰器的概念,允许我们无需使用继承或组合就能扩展任意可调用对象(函数、方法、类)的行为。