分析Python中设计模式之Decorator装饰器模式的要点


Posted in Python onMarch 02, 2016

先给出一个四人团对Decorator mode的定义:动态地给一个对象添加一些额外的职责。
再来说说这个模式的好处:认证,权限检查,记日志,检查参数,加锁,等等等等,这些功能和系统业务无关,但又是系统所必须的,说的更明白一点,就是面向方面的编程(AOP)。
在Python中Decorator mode可以按照像其它编程语言如C++, Java等的样子来实现,但是Python在应用装饰概念方面的能力上远不止于此,Python提供了一个语法和一个编程特性来加强这方面的功能。Python提供的语法就是装饰器语法(decorator),如下:

@aoo
def foo(): pass
def aoo(fn):
  return fn

装饰模式强调动态地给对象添加额外的功能。 Python内置了很多对装饰器的支持,因此在Python中使用装饰模式是非常容易的,下面是一个典型的例子,给函数增加日志功能:

import functools
def log_wrapper(fun):
 @functools.wraps(fun)
 def wrapper(*args, **kwargs):
  print '在函数执行前加日志'
  ret = fun(*args, **kwargs)
  print '在函数执行后家日志'
  return ret
 return wrapper


@log_wrapper
def test():
 print 'Hello, 世界'

functools.wraps是Python标准库提供的一个特殊的装饰器,用来解决装饰器带来的一些常规问题,如函数名称、doc等的不一致问题。@是Python针对装饰器提供的一个语法糖,上面的@log_wrapper相当于wrap_test = log_rapper(test),用@后,这个步骤由解释器代劳了。

装饰器是Python编程必须掌握的一项技能,在编码过程中经常会用到。

这里只是一个普通的内嵌函数

def foo(x):
  y = x
  def foo1 ():
    a = 1
    return a
  return foo1

而下面boo则是一个闭包

def aoo(a, b):
  c = a
  def boo (x):
    x = b + 1
    return x
  return boo

boo的特殊性在于引用了外部变量b,当aoo返回后,只要返回值(boo)一直存在,则对b的引用就会一直存在。
上面的知识可能需要花些时间消化,如果你觉得已经掌握了这些知识,下面就回归正题,看看这些语言特性是怎样来实现Python中装饰的概念的。
还是让我们先看一个简单的例子,然后逐步深入。这个例子就是加锁,怎样实现加锁的功能?
具体需求是这样的:我有一个对象,实现了某些功能并提供了一些接口供其它模块调用,这个对象是运行在并发的环境中的,因此我需要对接口的调用进行同步,第一版的代码如下:

class Foo(object):
  def __init__(self, …):
    self.lock = threading.Lock()
  def interface1(self, …):
    self.lock.acquire()
    try:
     do something
    finally:
     self.lock.release()
  def interface2(self, …):
    same as interface1()
  …

这版代码的问题很明显,那就是每个接口函数都有相同的加锁/解锁代码,重复的代码带来的是更多的键入,更多的阅读,更多的维护,以及更多的修改,最主要的是,程序员本应集中在业务上的的精力被分散了,而且请注意,真正的业务代码在距离函数定义2次缩进处开始,即使你的显示器是宽屏,这也会带来一些阅读上的困难。
你直觉的认为,可以把这些代码收进一个函数中,以达到复用的目的,但是请注意,这些代码不是一个完整同一的代码块,而是在中间嵌入了业务代码。
现在我们用装饰器语法来改进这部分代码,得到第2版代码:

def sync(func):
 def wrapper(*args, **kv):
   self = args[0]
   self.lock.acquire()
   try:
    return func(*args, **kv)
   finally:
    self.lock.release()
 return wrapper
class Foo(object):
  def __init__(self, …):
    self.lock = threading.Lock()
  @sync
  def interface1(self, …):
    do something
  @sync
  def interface2(self, …):
    do something
  …

一个装饰器函数的第一个参数是所要装饰的那个函数对象,而且装饰器函数必须返回一个函数对象。如sync函数,当其装饰interface1时,参数func的值就是interface1,返回值是wrapper,但类Foo实例的interface1被调用时,实际调用的是wrapper函数,在wrapper函数体中间接调用实际的interface1;当interface2被调用时,也调用的是wrapper函数,不过由于在装饰时func已经变成interface2,所以会间接地调用到实际的interface2函数。
使用装饰器语法的好处:
代码量大大的减少了,更少的代码意味着更少的维护,更少的阅读,更少的键入,好处不一而足(可复用,可维护)
用户基本上将绝大部分精力放在了业务代码上,而且少了加减锁的代码,可读性也提高了
缺点:
业务对象Foo中有一个非业务数据成员lock,很碍眼;
相当程度的耦合,wrapper的第一个参数必须是对象本身,而且被装饰的对象中必须有一个lock对象存在,这给客户对象添加了限制,使用起来不是很舒服。
我们可以更进一步想一想:
lock对象必须要放在Foo中吗?
为每个接口函数都键入@sync还是很烦人的重复性人工工作,如果漏添加一个,还是会造成莫名其妙的运行时错误,为什么不集中处理呢?
为了解决上述的缺点,第3版代码如下:

class DecorateClass(object):
 def decorate(self):
  for name, fn in self.iter():
   if not self.filter(name, fn):
    continue
   self.operate(name, fn)
class LockerDecorator(DecorateClass):
 def __init__(self, obj, lock = threading.RLock()):
  self.obj = obj
  self.lock = lock
 def iter(self):
  return [(name, getattr(self.obj, name)) for name in dir(self.obj)]
 def filter(self, name, fn):
  if not name.startswith('_') and callable(fn):
    return True
  else:
    return False
 def operate(self, name, fn):
  def locker(*args, **kv):
   self.lock.acquire()
   try:
    return fn(*args, **kv)
   finally:
    self.lock.release()
  setattr(self.obj, name, locker)
class Foo(object):
  def __init__(self, …):
    …
    LockerDecorator(self).decorate()
  def interface1(self, …):
    do something
  def interface2(self, …):
    do something
  …

对对象的功能装饰是一个更一般的功能,不仅限于为接口加锁,我用2个类来完成这一功能,DecorateClass是一个基类,只定义了遍历并应用装饰功能的算法代码(template method),LockerDecorator实现了为对象加锁的功能,其中iter是迭代器,定义了怎样遍历对象中的成员(包括数据成员和成员函数),filter是过滤器,定义了符合什么规则的成员才能成为一个接口,operate是执行函数,具体实施了为对象接口加锁的功能。
而在业务类Foo的__init__函数中,只需要在最后添加一行代码:LockerDecorator(self).decorate(),就可以完成为对象加锁的功能。
如果你的对象提供的接口有特殊性,完全可以通过直接改写filter或者继承LockerDecorator并覆盖filter的方式来实现;此外,如果要使用其他的装饰功能,可以写一个继承自DecorateClass的类,并实现iter,filter和operate三个函数即可。

Python 相关文章推荐
python解析json实例方法
Nov 19 Python
python网络编程学习笔记(一)
Jun 09 Python
python web框架学习笔记
May 03 Python
Windows上使用Python增加或删除权限的方法
Apr 24 Python
python实现将汉字保存成文本的方法
Nov 16 Python
python3爬虫获取html内容及各属性值的方法
Dec 17 Python
详解django2中关于时间处理策略
Mar 06 Python
在pycharm下设置自己的个性模版方法
Jul 15 Python
解决Tensorflow 使用时cpu编译不支持警告的问题
Feb 03 Python
python 瀑布线指标编写实例
Jun 03 Python
virtualenv介绍及简明教程
Jun 23 Python
jupyter notebook 写代码自动补全的实现
Nov 02 Python
实例解析Python设计模式编程之桥接模式的运用
Mar 02 #Python
Python随机生成带特殊字符的密码
Mar 02 #Python
Python设计模式编程中Adapter适配器模式的使用实例
Mar 02 #Python
Python打造出适合自己的定制化Eclipse IDE
Mar 02 #Python
设计模式中的原型模式在Python程序中的应用示例
Mar 02 #Python
深入解析Python设计模式编程中建造者模式的使用
Mar 02 #Python
举例讲解Python设计模式编程中对抽象工厂模式的运用
Mar 02 #Python
You might like
php数组函数序列之array_values() 获取数组元素值的函数与方法
2011/10/30 PHP
php 解决旧系统 查出所有数据分页的类
2012/08/27 PHP
php笔记之:php函数range() round()和list()的使用说明
2013/04/26 PHP
PHP实现Google plus的好友拖拽分组效果
2016/10/21 PHP
关于php支持的协议与封装协议总结(推荐)
2017/11/17 PHP
客户端脚本中常常出现的一些问题和调试技巧
2007/01/09 Javascript
javascript 表单规则集合对象
2009/07/21 Javascript
JavaScript Event学习第四章 传统的事件注册模型
2010/02/07 Javascript
输入框的字数时时统计—关于 onpropertychange 和 oninput 使用
2011/10/21 Javascript
node.js中的url.resolve方法使用说明
2014/12/10 Javascript
JS实现状态栏跑马灯文字效果代码
2015/10/24 Javascript
JavaScript实现弹出DIV层同时页面背景渐变成半透明效果
2016/03/25 Javascript
javascript模块化简单解析
2016/04/07 Javascript
jq实现左滑显示删除按钮,点击删除实现删除数据功能(推荐)
2016/08/23 Javascript
Nodejs下DNS缓存问题浅析
2016/11/16 NodeJs
jQuery插件HighCharts实现的2D回归直线散点效果示例【附demo源码下载】
2017/03/09 Javascript
ECMAscript 变量作用域总结概括
2017/08/18 Javascript
Vue 2.0学习笔记之使用$refs访问Vue中的DOM
2017/12/19 Javascript
JS实现左边列表移到到右边列表功能
2018/03/28 Javascript
vue2.0页面前进刷新回退不刷新的实现方法
2018/07/31 Javascript
Vue模板语法中数据绑定的实例代码
2019/05/17 Javascript
vue router 跳转时打开新页面的示例方法
2019/07/28 Javascript
vue如何在用户要关闭当前网页时弹出提示的实现
2020/05/31 Javascript
[01:52]2014DOTA2西雅图邀请赛 V社开大会你不知道的小秘密
2014/07/08 DOTA
[07:54]DOTA2 MV《我的动力鞋》 ImbaTV 出品
2014/11/21 DOTA
解决新版Pycharm中Matplotlib图像不在弹出独立的显示窗口问题
2019/01/15 Python
python+selenium实现QQ邮箱自动发送功能
2019/01/23 Python
Django数据库类库MySQLdb使用详解
2019/04/28 Python
基于python和flask实现http接口过程解析
2020/06/15 Python
Python实现删除某列中含有空值的行的示例代码
2020/07/20 Python
iHerb中文官网:维生素、保健品和健康产品
2018/11/01 全球购物
函授本科自我鉴定
2014/02/04 职场文书
十佳护士获奖感言
2014/02/18 职场文书
超市开店计划书
2014/04/26 职场文书
工商行政管理专业求职书
2014/05/23 职场文书
python 解决微分方程的操作(数值解法)
2021/05/26 Python