分析Python中设计模式之Decorator装饰器模式的要点
作者:tqsummer 发布时间:2021-12-06 12:04:01
先给出一个四人团对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三个函数即可。


猜你喜欢
- 本文实例为大家分享了js浏览器倒计时跳转页面效果,供大家参考,具体内容如下效果图:<!DOCTYPE html><html
- java往php传数据最近刚好做到了这一块,有php调用java的接口来返回数据,php在做数据的处理可以做到两个系统的数据库同步操作,一般
- 一、当我们用Python matplot时作图时,一些数据需要以百分比显示,以更方便地对比模型的性能提升百分比。二、借助matplotlib
- 一、下载PyCharm下载最新版PyCharm,官方地址:https://www.jetbrains.com/pycharm/downloa
- 有时需要读取jpg图像的长和宽,tensorflow提供了很好的支持直接上示例decode_jpeg_data = tf.placehold
- 设计图是这样:可是做出来是这样:出现了一行连着。。要知道工作上总有些ui没事做喜欢指指点点。后来翻查官方手册发现了这个参数:附上网址:htt
- 可以在Mac OS X 10.2.x(“Jaguar”)和以上版本上Mac OS X使用二进制安装软
- 直接看实例。例1 重新加载js文件function loadJs(file) {
- 用XMLHTTP Post Form时的表单乱码有两方面的原因——Post表单数据时中文乱码;服务器Response被XMLHTTP不正确编
- 实例如下:$("#stream_title").val().trim().replace(/\s/g,"&qu
- python 的fnmatch 还真是省心,相比于 java 中的FilenameFilter ,真是好太多了,你完成不需要去实现什么接口。
- 本文简介前段时间,黄同学写了一篇《MySQL窗口实战》文章(文章如下),但是里面大多数是以实战练习为主,没有做详细的解释。传送门:MySQL
- 我经常需要用Python与solr进行异步请求工作。这里有段代码阻塞在Solr http请求上, 直到第一个完成才会执行第二个请
- 编码规范Python 编码规范重要性的原因用一句话来概括就是:统一的编码规范可以提高开发效率。无论你是 编程者,还是 阅读者,好的规范能让你
- Python内存管理一、对象池1.小整数池系统默认创建好的,等着你使用概述:整数在程序中的使用非常广泛,Python为了优化速度,使用了小整
- 前言为了参加某个作秀活动,研究了一波如何结合小程序、科大讯飞实现语音录入、识别的实现。科大讯飞开发文档中只给出 Python 的 demo,
- 问题描述: 附加数据时,提示无法打开物理文件,操作系统错误5。如下图:问题原因:可能是文件访
- javascript中给数组加元素是一个非常简单的问题,javascript本身就提供了大量这类函数,我们可以使用js自带函数快速给数组增加
- 本文实例为大家分享了python实现邮件自动发送的具体代码,供大家参考,具体内容如下case 1:纯文本和HTML文件发送# -*- cod
- 前言利用Python的ffmpy库提取视频中的音频。本文提供工具类代码。环境依赖需要安装ffmpy,安装指令:pip install ffm