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

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

使用组合

使用继承

与继承相比,通常应该优先选择组合,因为继承使得代码更难复用,继承关系是静态的,并且应用于整个类以及这个类的所有实例。

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

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

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

现实生活的例子

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

软件的例子

Django框架大量地使用修饰器,其中一个例子是视图修饰器。Django的试图(View)修饰器可用于以下几种用途。

限制某些HTTP请求对视图的访问

控制特定视图上的缓存行为

按单个视图控制压缩

基于特定HTTP请求头控制缓存

Grok框架也使用修饰器来实现不同的目标,比如:

将一个函数注册为事件订阅者

以特定权限保护一个方法

实现适配器模式

应用案例

当用于实现横切关注点(cross-cutting concerns)时,修饰器模式会大显神威。以下是横切关注点的一些例子。

数据校验

事务处理(这里类似数据库事务,要么所有步骤都成功完成,要么事务失败)

缓存

日志

监控

调试

业务规则

压缩

加密

一般来说,应用中有些部件是通用的,可应用于其他部件,这样的部件被看做横切关注点。

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

实现

Python修饰器通用并且非常强大。你可以在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个斐波那契数要花费11秒,实用memoization方法看看能否改善。在下面的代码中,我们使用一个dict来缓存斐波那契数列中已经计算好的数值,同时也修改传给fabonacci()函数的参数,计算第100个菲波那切数,而不是第8个(文件fibonacci.py)。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的代码实现,可以看到性能得到了极大的提升,但代码也没有不使用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) @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() ))

看看我们数学模块的完整代码(mymath.py)和执行的样例输出。

Q1:多个修饰器时,执行顺序如何?

输出:outer start inner start 10 inner end outer end

Q2:修饰器能否有参数?

Answer:可以有参数,例如这个例子True就返回被修饰的函数f,False返回不修饰的原函数。question02.py

python 修饰器 教程_python 实现 修饰器模式相关推荐

  1. python浏览器使用教程_python+selenium--浏览器操作的基本方法

    关于自动化测试其实很多时候都是为了节省时间做一些大批量的复用性工作,在正式学习自动化脚本编写之前,这里引用陈建忠老师的博客内容,希望大家能了解一下关于什么是自动化的知识,在日后做自动化测试的过程中会根 ...

  2. python开发音乐播放器教程_python开发简易版在线音乐播放器示例代码

    在线音乐播放器,使用python的Tkinter库做了一个界面,感觉这个库使用起来还是挺方便的,音乐的数据来自网易云音乐的一个接口,通过urllib.urlopen模块打开网址,使用Json模块进行数 ...

  3. python修饰符作用_python函数修饰符@的使用

    python函数修饰符@的作用是为现有函数增加额外的功能,常用于插入日志.性能测试.事务处理等等. 创建函数修饰符的规则: (1)修饰符是一个函数 (2)修饰符取被修饰函数为参数 (3)修饰符返回一个 ...

  4. python新闻爬虫教程_python简易爬虫教程--(一)批量获取搜狐新闻

    我们先从简单的抓取文本信息开始,来写我们的第一个爬虫程序,获取搜狐新闻的内容. 我们首先来介绍一下我们需要用到的库. 爬虫程序的步骤,一般可以分为三步: 1.获取网页源码(html源码): 2.从代码 ...

  5. python独立网站教程_python做网站教程_如何免费做网站的教程

    python学习指南教程 180x270 - 7KB - JPEG 图灵程序设计丛书:Python基础教程 260x317 - 12KB - JPEG 跳一跳python使用教程 微信跳一跳pytho ...

  6. python入门测试教程_Python测试入门

    python入门测试教程 This tutorial is for anyone who has written a fantastic application in Python but hasn' ...

  7. python 微信机器人教程_Python创建微信机器人(附赠Python视频教程)

    微信,一个日活10亿的超级app,不仅在国内社交独领风骚,在国外社交也同样占有一席之地.今天我们要讲的便是如何用Python来做一个微信聊天机器人,突然想起鲁迅先生曾经说过的一句话: 因为是微信机器人 ...

  8. python自学完整教程_python自学完整教程

    [教程介绍] 想学习一门编程语言,是不是必须考上大学的计臬机专业,才能真正学会呢? 其实不是,在我们身边,很多初中生,甚至国外不少小学生都能把编程学好,通过自学,很多人都能成为编程高手. python ...

  9. python闭包函数使用教程_Python闭包装饰器使用方法汇总

    闭包内容: 匿名函数:能够完成简单的功能,传递这个函数的引用,只有功能 普通函数:能够完成复杂的功能,传递这个函数的引用,只有功能 闭包:能够完成较为复杂的功能,传递这个闭包中的函数以及数据,因此传递 ...

最新文章

  1. hashtable、hashmap、ConcurrentHashMap、treemap的区别
  2. 小白学python,零基础学Python难不难?
  3. linux accept 队列,[译] TCP的SYN队列和Accept队列
  4. WebKit如何加载web页面
  5. Oracle-查看oracle是否有表被锁
  6. 论文浅尝 | 利用问题生成提升知识图谱问答
  7. C#编程(八十一)---------- 捕获异常
  8. 漫谈Servlet(一)
  9. PowerDesigner建立与数据库的连接,以便生成数据库和从数据库生成到PD中
  10. smart模版学习笔记一
  11. 网络链接错误,请检查配置后重试!
  12. Android点阵屏效果的控件
  13. python的repl模式_为什么解释器的交互模式又叫 REPL
  14. 方差分析分类及SAS实现代码
  15. 数据库性能调优架构师所需的skillset
  16. 比特大陆内部究竟发生了什么?
  17. “大数据”离我们有多近?听听华为怎么说!
  18. FFA 2022 收官总结|活动报告出炉,实时即未来!
  19. 关于“茴香豆的‘茴’有几种写法”:学习过程中,若时间精力有限则优先记住最好用的一种
  20. 天下3服务器全部显示test,《天下》iOS品鉴 Test Flight 用户使用流程

热门文章

  1. FinClip 小程序插件开发大赛开启,十万奖金池等你来拿
  2. 2020:苏格兰地图绘制
  3. 网络变压器、网络隔离变压器终端接线应用
  4. 重金属污染源matlab代码,2011重金属污染源的数学建模.docx
  5. Qt编写自定义控件4-旋转仪表盘
  6. XPath-语法大全
  7. Unity 之 虚拟现实的UI制作和交互
  8. 鲲鹏物理服务器如何安装操作系统,服务器如何安装操作系统
  9. F#基础篇_(1)F#简介
  10. Android CPU使用率