python设计模式之修饰器模式

无论何时我们想对一个对象添加额外的功能,都有下面这些不同的可选方法。

[ ] 如果合理,可以直接将功能添加到对象所属的类(例如,添加一个新的方法)

[ ] 使用组合

[ ] 使用继承

设计模式为我们提供第四种可选方法,以支持动态地(运行时)扩展一个对象的功能,这种方法就是修饰器。 修饰器( Decorator)模式能够以透明的方式(不会影响其他对象)动态地将功能添加到一个对象中。

在许多编程语言中,使用子类化(继承)来实现修饰器模式。在Python中,我们可以(并且应该)使用内置的修饰器特性。一个Python修饰器就是对Python语法的一个特定改变,用于扩展一个类、方法或函数的行为,而无需使用继承。从实现的角度来说,Python修饰器是一个可调用对象(函数、方法、类),接受一个函数对象fin作为输入,并返回另一 个函 数对象 fout。这意味着可以将任何具有这些属性的可调用对象当作一个修饰器。

修饰器模式和Python修饰器之间并不是一对一的等价关系。 Python修饰器能做的实际上比修饰器模式多得多,其中之一就是实现修饰器模式 。

1. 现实生活中的例子

该模式虽名为修饰器,但这并不意味着它应该只用于让产品看起来更漂亮。修饰器模式通常用于扩展一个对象的功能。这类扩展的实际例子有,给枪加一个消音器、使用不同的照相机镜头等。

2. 软件的例子

Django框架大量地使用修饰器,其中一个例子是视图修饰器。

3. 应用案例

使用修饰器模式的一个常见例子是图形用户界面( Graphical User Interface, GUI)工具集。在一个GUI工具集中,我们希望能够将一些特性,比如边框、阴影、颜色以及滚屏,添加到单个组件/部件 。

4. 实现(建议先看看我之前写的《关于python装饰器结合递归的理解》一篇随笔便于理解)

实现一个memoization修饰器。所有递归函数都能因memoization而提速,那么来试试常用的斐波那契数列例子。使用递归算法实现斐波那契数列,直接了当,但性能问题较大,即使对于很小的数值也是如此。首先来看看朴素的实现方法(文件fibonacci_naive.py)。

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(8)', 'from __main__ import fibonacci')

print(t.timeit())

执行一下这个例子就知道这种实现的速度有多慢了。计算第8个斐波那契数要花费17秒。运行的样例输出如下所示。

16.669270177000726

使用memoization方法看看能否改善。在下面的代码中,我们使用一个dict来缓存斐波那契数列中已经计算好的数值,同时也修改传给fabonacci()函数的参数,计算第100个斐波那契数,而不是第8个。

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(100)', 'from __main__ import fibonacci')

print(t.timeit())

执行基于memoization的代码实现,可以看到性能得到了极大的提升,甚至对于计算大的数值性能也是可接受的。运行的样例输出如下所示。

0.31532211999729043

但这种方法有一些问题。虽然性能不再是一个问题,但代码也没有不使用memoization时那样简洁。如果我们决定扩展代码,加入更多的数学函数,并将其转变成一个模块,那又会是什么样的呢?假设决定加入的下一个函数是nsum(),该函数返回前n个数字的和。注意这个函数已存在于math模块中,名为fsum(),但我们也能很容易就能想到标准库中还没有、但是对我们模块有用的其他函数(例如,帕斯卡三角形、埃拉托斯特尼筛法等)。所以暂且不必在意示例函数是否已存在。使用memoization实现nsum()函数的代码如下所示。

known_sum = {0:0}

def nsum(n):

assert(n >= 0), 'n must be >= 0'

if n in known_sum:

return known_sum[n]

res = n + nsum(n-1)

known_sum[n] = res

return res

你有没有注意到其中的问题?多了一个名为known_sum的新字典,为nsum提供缓存作用,并且函数本身也比不使用memoization时的更复杂。这个模块逐步变得不必要地复杂。保持递归函数与朴素版本的一样简单,但在性能上又能与使用memoization的函数相近,这可能吗?幸运的是,确实可能,解决方案就是使用修饰器模式。

首先创建一个如下面的例子所示的memoize()函数。这个修饰器接受一个需要使用memoization的函数fn作为输入,使用一个名为known的dict作为缓存。函数functools.wraps()是一个为创建修饰器提供便利的函数;虽不强制,但推荐使用,因为它能保留被修饰函数的文档和签名。这种情况要求参数列表args,因为被修饰的函数可能有输入参数。如果fibonacci()和nsum()不需要任何参数,那么使用args确实是多余的,但它们是需要参数n的。

import functools

def memoize(fn):

known = dict()

@functools.wraps(fn)

def memoizer(*args):

if args not in known:

known[args] = fn(*args)

return known[args]

return memoizer

现在,对朴素版本的函数应用memoize()修饰器。这样既能保持代码的可读性又不影响性能。我们通过修饰(或修饰行)来应用一个修饰器。修饰使用@name语法,其中name是指我们想要使用的修饰器的名称。这其实只不过是一个简化修饰器使用的语法糖。来看看下面的例子中如何对我们的递归函数使用memoize()修饰器。

@memoize

def nsum(n):

'''返回前n个数字的和'''

assert(n >= 0), 'n must be <= 0'

return 0 if n == 0 else n + nsum(n-1)

@memoize

def fibonacci(n):

'''返回斐波那契数列的第n个数'''

assert(n >= 0), 'n must be >= 0'

return n if n in (0, 1) else fibonacci(n-1) + fibonacci(n-2)

代码的最后一部分展示如何使用被修饰的函数,并测量其性能。 measure是一个字典列表,用于避免代码重复。注意__name__和__doc__分别是如何展示正确的函数名称和文档字符串值的。尝试memoize()中删除@functools.wraps(fn)修饰,看看是否仍旧如此 。

if __name__ == '__main__':

from timeit import Timer

measure = [ {'exec':'fibonacci(100)', 'import':'fibonacci',

'func':fibonacci},{'exec':'nsum(200)', 'import':'nsum',

'func':nsum} ]

for m in measure:

t = Timer('{}'.format(m['exec']), 'from __main__ import{}'.format(m['import']))

print('name: {}, doc: {}, executing: {}, time{}'.format(m['func'].__name__, m['func'].__doc__,m['exec'], t.timeit()))

完整代码及输出:

import functools

def memoize(fn):

known = dict()

@functools.wraps(fn)

def memoizer(*args):

if args not in known:

known[args] = fn(*args)

return known[args]

return memoizer

@memoize

def nsum(n):

'''返回前n个数字的和'''

assert(n >= 0), 'n must be >= 0'

return 0 if n == 0 else n + nsum(n-1)

@memoize

def fibonacci(n):

'''返回斐波那契数列的第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

measure = [ {'exec':'fibonacci(100)', 'import':'fibonacci','func':fibonacci},{'exec':'nsum(200)', 'import':'nsum','func':nsum} ]

for m in measure:

t = Timer('{}'.format(m['exec']), 'from __main__ import{}'.format(m['import']))

print('name: {}, doc: {}, executing: {}, time{}'.format(m['func'].__name__, m['func'].__doc__,m['exec'], t.timeit()))

输出:

name: fibonacci, doc: Returns the nth number of the Fibonacci

sequence, executing: fibonacci(100), time: 0.4169441329995607

name: nsum, doc: Returns the sum of the first n numbers,

executing: nsum(200),

5. 小结

我们使用修饰器模式来扩展一个对象的行为,无需使用继承,非常方便。 Python进一步扩展了修饰器的概念,允许我们无需使用继承或组合就能扩展任意可调用对象(函数、方法或类)的行为。我们可以使用Python内置的修饰器特性。

python修饰器_python设计模式之修饰器模式相关推荐

  1. python 装饰器 继承_Python设计模式之装饰器模式

    装饰器模式 无论何时我们想对一个对象添加额外的功能,都有下面这些不同的可选方法. 如果合理,可以直接将功能添加到对象所属的类(例如,添加一个新的方法) 使用组合 使用继承 注意,本文中的Decorat ...

  2. python中的装饰器、装饰器模式_python 设计模式之装饰器模式 Decorator Pattern

    #写在前面 已经有一个礼拜多没写博客了,因为沉醉在了<妙味>这部小说里,里面讲的是一个厨师苏秒的故事.现实中大部分人不会有她的天分.我喜欢她的性格:总是想着去解决问题,好像从来没有怨天尤人 ...

  3. python高级语法装饰器_Python高级编程——装饰器Decorator超详细讲解上

    Python高级编程--装饰器Decorator超详细讲解(上篇) 送你小心心记得关注我哦!! 进入正文 全文摘要 装饰器decorator,是python语言的重要特性,我们平时都会遇到,无论是面向 ...

  4. python核心装饰_Python核心编程 | 装饰器

    装饰器是程序开发的基础知识,用好装饰器,在程序开发中能够提高效率 它可以在不需要修改每个函数内部代码的情况下,为多个函数添加附加功能,如权限验证,log日志等 涉及点: 1.先梳理一下 >> ...

  5. python的网页解析器_python 之网页解析器

    一.什么是网页解析器 1.网页解析器名词解释 首先让我们来了解下,什么是网页解析器,简单的说就是用来解析html网页的工具,准确的说:它是一个HTML网页信息提取工具,就是从html网页中解析提取出& ...

  6. python自带网页解析器_python 之网页解析器

    一.什么是网页解析器 1.网页解析器名词解释 首先让我们来了解下,什么是网页解析器,简单的说就是用来解析html网页的工具,准确的说:它是一个HTML网页信息提取工具,就是从html网页中解析提取出& ...

  7. python什么是装饰器_python学习之装饰器是什么?

    学完python函数,接下来我们跟着猿人学python一起来看看什么是装饰器. 大家应该多多少少在其它地方有看过或是听过"装饰器"这个词.装饰器,顾名思义,就是用来装饰的,它装饰的 ...

  8. python类是实例的工厂_Python设计模式之工厂方法模式实例详解

    本文实例讲述了Python设计模式之工厂方法模式.分享给大家供大家参考,具体如下: 工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定实例化哪一个类, ...

  9. python抽象工厂模式_Python设计模式之抽象工厂模式

    Python设计模式之抽象工厂模式 这篇文章主要为大家详细介绍了Python设计模式之抽象工厂模式,感兴趣的小伙伴们可以参考一下 python面向对象编程入门,我们需要不断学习进步 "&qu ...

最新文章

  1. 在echarts中自定义提示框内容
  2. 本是同根生,相煎何太急?
  3. ***不是已知元素 原因可能是网站中存在编译错误
  4. mysql 当前排名查询,MySQL排名查询分配
  5. java 自带导出excel_4.java项目页面导出excel功能
  6. GD32使用ST的HAL库和GD官方库的一些体会
  7. java系统化基础-day02-运算符、选择结构、循环结构
  8. linux烧录到手机,新人求教,怎么烧录Linux系统到一个小芯片上?
  9. 携程2017 校招编程题
  10. html字体铺盖颜色,买被子也是有讲究的?这几种颜色的被子,再好看都别往卧室放!...
  11. 为了摸鱼,我开发了一个工具网站
  12. 浅谈工程总承包项目WBS的重要性与创建方法
  13. CentOS_7环境搭建L2TP服务器
  14. 玩安卓从 0 到 1 之总体概览
  15. Android开发者选项——GPU呈现模式分析
  16. 通过支付宝服务中断事件看系统可靠性和YunOS的可靠性
  17. 【NoteBook】吴晓波:《影响商业的50本书》——第一部分:当商业开始改变世界
  18. unity3d手游逆向
  19. 计算机无法检查出来的错误,Win7系统电脑显卡出现错误时怎么检测(图文)
  20. MQTT协议之中国移动ONENET

热门文章

  1. JS学习日志15 -- JS基础--忍者代码
  2. 《苏宁安全架构演进及实践》阅读有感
  3. python怎么识别鼠标的动作_Python捕捉和模拟鼠标事件的方法
  4. 斧子展示_用斧头进行自动辅助功能检查
  5. MSM8909读PMIC芯片寄存器
  6. 34寸显示器作Mac外接怎么用才最爽
  7. Oracle 12c CDBPDBs管理
  8. Django 多方式实现跨域访问
  9. 面试结束时要说的那句话
  10. mknod用法以及主次设备号