Python-理解装饰器

文章先由stackoverflow上面的一个问题引起吧,如果使用如下的代码:

@makebold
@makeitalic
def say():return "Hello"

打印出如下的输出:

<b><i>Hello<i></b>

你会怎么做?最后给出的答案是:

def makebold(fn):def wrapped():return "<b>" + fn() + "</b>"return wrappeddef makeitalic(fn):def wrapped():return "<i>" + fn() + "</i>"return wrapped@makebold
@makeitalic
def hello():return "hello world"print hello() ## 返回 <b><i>hello world</i></b>

现在我们来看看如何从一些最基础的方式来理解Python的装饰器。英文讨论参考  Here 。
1.1. 需求是怎么来的?

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

装饰器的定义很是抽象,我们来看一个小例子。

def foo():print 'in foo()'
foo()

这是一个很无聊的函数没错。但是突然有一个更无聊的人,我们称呼他为B君,说我想看看执行这个函数用了多长时间,好吧,那么我们可以这样做:

import time
def foo():start = time.clock()print 'in foo()'end = time.clock()print 'used:', end - startfoo()

很好,功能看起来无懈可击。可是蛋疼的B君此刻突然不想看这个函数了,他对另一个叫foo2的函数产生了更浓厚的兴趣。

怎么办呢?如果把以上新增加的代码复制到foo2里,这就犯了大忌了~复制什么的难道不是最讨厌了么!而且,如果B君继续看了其他的函数呢?
1.2. 以不变应万变,是变也  

还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将foo的引用传递给他,然后在timeit中调用foo并进行计时,这样,我们就达到了不改动foo定义的目的,而且,不论B君看了多少个函数,我们都不用去修改函数定义了!

import timedef foo():print 'in foo()'def timeit(func):start = time.clock()func()end =time.clock()print 'used:', end - starttimeit(foo)

看起来逻辑上并没有问题,一切都很美好并且运作正常!……等等,我们似乎修改了调用部分的代码。原本我们是这样调用的:foo(),修改以后变成了:timeit(foo)。这样的话,如果foo在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。
1.3. 最大限度地少改动!  

既然如此,我们就来想想办法不修改调用的代码;如果不修改调用代码,也就意味着调用foo()需要产生调用timeit(foo)的效果。我们可以想到将timeit赋值给foo,但是timeit似乎带有一个参数……想办法把参数统一吧!如果timeit(foo)不是直接产生调用效果,而是返回一个与foo参数列表一致的函数的话……就很好办了,将timeit(foo)的返回值赋值给foo,然后,调用foo()的代码完全不用修改!

#-*- coding: UTF-8 -*-
import timedef foo():print 'in foo()'# 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法
def timeit(func):# 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装def wrapper():start = time.clock()func()end =time.clock()print 'used:', end - start# 将包装后的函数返回return wrapperfoo = timeit(foo)
foo()

这样,一个简易的计时器就做好了!我们只需要在定义foo以后调用foo之前,加上foo = timeit(foo),就可以达到计时的目的,这也就是装饰器的概念,看起来像是foo被timeit装饰了。在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。
这个例子仅用于演示,并没有考虑foo带有参数和有返回值的情况,完善它的重任就交给你了 :)

上面这段代码看起来似乎已经不能再精简了,Python于是提供了一个语法糖来降低字符输入量。

import timedef timeit(func):def wrapper():start = time.clock()func()end =time.clock()print 'used:', end - startreturn wrapper@timeit
def foo():print 'in foo()'foo()

重点关注第11行的@timeit,在定义上加上这一行与另外写foo = timeit(foo)完全等价,千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。

1.4 最后回答前面提到的问题:

# 装饰器makebold用于转换为粗体
def makebold(fn):# 结果返回该函数def wrapper():# 插入一些执行前后的代码return "<b>" + fn() + "</b>"return wrapper# 装饰器makeitalic用于转换为斜体
def makeitalic(fn):# 结果返回该函数def wrapper():# 插入一些执行前后的代码return "<i>" + fn() + "</i>"return wrapper
# 注意顺序
@makebold
@makeitalic
def say():return "hello"print say()
#输出: <b><i>hello</i></b># 等同于
def say():return "hello"
say = makebold(makeitalic(say))print say()
#输出: <b><i>hello</i></b>

2、装饰器的种类

2.1 无参数装饰器

def deco(func):print funcreturn func
@deco
def foo():pass
foo()

第一个函数deco是装饰函数,它的参数就是被装饰的函数对象。我们可以在deco函数内对传入的函数对象做一番“装饰”,然后返回这个对象(记住一定要返回 ,不然外面调用foo的地方将会无函数可用。实际上此时foo=deco(foo)

我写了个小例子,检查函数有没有说明文档:

def deco_functionNeedDoc(func):if func.__doc__ == None :print func, "has no __doc__, it's a bad habit."else:print func, ':', func.__doc__, '.'return func
@deco_functionNeedDoc
def f():print 'f() Do something'
@deco_functionNeedDoc
def g():'I have a __doc__'print 'g() Do something'
f()
g()

2.2 有参数装饰器

def decomaker(arg):'通常对arg会有一定的要求'"""由于有参数的decorator函数在调用时只会使用应用时的参数而不接收被装饰的函数做为参数,所以必须在其内部再创建一个函数"""def newDeco(func):    #定义一个新的decorator函数print func, argreturn funcreturn newDeco
@decomaker(deco_args)
def foo():pass
foo()

第一个函数decomaker是装饰函数,它的参数是用来加强“加强装饰”的。由于此函数并非被装饰的函数对象,所以在内部必须至少创建一个接受被装饰函数的函数,然后返回这个对象(实际上此时foo=decomaker(arg)(foo))

这个我还真想不出什么好例子,还是见识少啊,只好借用同步锁的例子了:

def synchronized(lock):"""锁同步装饰方法!lock必须实现了acquire和release方法"""def sync_with_lock(func):def new_func(*args, **kwargs):lock.acquire()try:return func(*args, **kwargs)finally:lock.release()new_func.func_name = func.func_namenew_func.__doc__ = func.__doc__return new_funcreturn sync_with_lock
@synchronized(__locker)
def update(data):
"""更新计划任务"""tasks = self.get_tasks()delete_task = Nonefor task in tasks:if task[PLANTASK.ID] == data[PLANTASK.ID]:tasks.insert(tasks.index(task), data)tasks.remove(task)delete_task = taskr, msg = self._refresh(tasks, delete_task)return r, msg, data[PLANTASK.ID]

调用时还是updae(data)。

同时还可以将多个装饰器组合 使用,注意调用顺序:

@synchronized(__locker)
@deco_functionNeedDoc
def f():print 'f() Do something'

2.3 内置的装饰器

内置的装饰器有三个,分别是staticmethod、classmethod和property,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。由于模块里可以定义函数,所以静态方法和类方法的用处并不是太多,除非你想要完全的面向对象编程。而属性也不是不可或缺的,Java没有属性也一样活得很滋润。从我个人的Python经验来看,我没有使用过property,使用staticmethod和classmethod的频率也非常低。

2.4 装饰器进阶

http://www.cnblogs.com/JohnABC/p/4186209.html


具体请参考: http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html
转自: http://www.open-open.com/lib/view/open1374584644558.html

转载于:https://www.cnblogs.com/JohnABC/p/4091532.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/292550.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

收集网络状态(Ping),并用邮件通知管理员

在没有第三方工具对网络进行监控的话&#xff0c;要检查网络中某台主机&#xff0c;或是某个IP地址通讯是否正常&#xff0c;我们通常用手动PING来进行测试。有了PowerShell&#xff0c;我们可以用他定时Ping网络上的几个IP地址&#xff0c;然后把ping的个延时时间用邮件通知给…

sql 某列数据全部为0则不显示该列_数据产品经理养成记(五):汇总分析

学会了如何查找数据后&#xff0c;接下来就要对数据进行分析处理&#xff0c;比如求和、平均值、加总等等。这些对数据的加工处理通过汇总函数来实现。汇总函数在之前的两篇文章中都有涉及&#xff0c;这里采用概念--案例--总结的方式&#xff0c;集中介绍一下。1.什么是汇总函…

如何通过 反射 调用某个对象的私有方法?

咨询区 Jeromy Irvine我的类中有一组私有方法&#xff0c;我现在想根据灵活的输入值来动态调用其中的私有方法&#xff0c;代码类似是这个样子。MethodInfo dynMethod this.GetType().GetMethod("Draw_" itemType); dynMethod.Invoke(this, new object[] { methodP…

vim学习日志(5):vim下wimrc的配置,解决中文乱码问题

解决linux下vim乱码的情况&#xff1a;(修改vimrc的内容&#xff09; 全局的情况下&#xff1a;即所有用户都能用这个配置 文件地址&#xff1a;/etc/vimrc 在文件中添加&#xff1a; set fileencodingsutf-8,ucs-bom,gb18030,gbk,gb2312,cp936 set termencodingutf-8 set enco…

Android插件化开发之AMS与应用程序(客户端ActivityThread、Instrumentation、Activity)通信模型分析

转载来自&#xff1a;http://blog.csdn.net/qinjuning/article/details/7262769 今天主要分析下ActivityManagerService(服务端) 与应用程序(客户端)之间的通信模型&#xff0c;在介绍这个通信模型的基础上&#xff0c;再 简单介绍实现这个模型所需要数据类型。 本文所介绍内容…

深入了解JavaScript中的正则表达式构造函数和正则表达式字面量

正则表达式是在处理字符串时非常有用的工具&#xff0c;它可以帮助我们进行模式匹配、搜索和替换操作。在JavaScript中&#xff0c;我们可以使用正则表达式构造函数 RegExp 或正则表达式字面量来创建正则表达式对象。本文将深入探讨这两种方式的不同之处&#xff0c;并通过代码…

iOS开发UI篇—直接使用UITableView Controller

iOS开发UI篇—直接使用UITableView Controller 一、一般过程 1 //2 // YYViewController.h3 // UITableView Controller4 //5 // Created by 孔医己 on 14-6-2.6 // Copyright (c) 2014年 itcast. All rights reserved.7 //8 9 #import <UIKit/UIKit.h> 10 11 inter…

怎么做图片文字二维码一起_怎么做?才能让文字编排更出彩

在之前视觉设计文章中&#xff0c;我把视觉设计大致罗列了四个方向&#xff0c;更多的是希望能够为大家带来一些努力方向&#xff0c;在设计的路上不那么困惑迷茫&#xff0c;视觉设计本身涵盖的范围就比较广&#xff0c;同时也没有什么衡量的标准和具体的特征&#xff0c;只有…

×××

的规则如下&#xff1a;任意的5张牌&#xff0c;只要其中的三张能凑满10的整数倍&#xff0c;就算有牛&#xff0c;否则没牛。在有牛的前提下&#xff0c;另外两张牌相加取个位数上的数字&#xff0c;数字是几就是牛几。且数字越大的一方胜。碰到数字相当的情况下&#xff0c;就…

.NET6之MiniAPI(八):日志

说明&#xff1a;本篇简单说一下日志中常用的几个点&#xff0c;关于日志&#xff0c;后面重点会说到三方日志提供程序在MiniAPI中&#xff0c;可以通过方法或构造函数中&#xff0c;获取框架自动注入的日志类型&#xff0c;如下方式&#xff1a;app.MapGet("/test",…

为什么你闻不到自己胳肢窝的味道?

▲ 点击查看生活中&#xff0c;我们常常会选择性地忽略一些事。吃螺蛳粉的人不会觉得屋子臭&#xff0c;而别人身上有一点烟味就可以闻到。公司的厕所&#xff0c;别人用完后&#xff0c;总觉得比自己用完时更臭。夏天胳肢窝出汗的味道&#xff0c;自己从来都闻不到&#xff0c…

Android插件化开发之Hook StartActivity方法

第一步、先爆项目demo照片&#xff0c;代码不多&#xff0c;不要怕 第二步、应该知道Java反射相关知识 如果不知道或者忘记的小伙伴请猛搓这里&#xff0c;Android插件化开发基础之Java反射机制研究 http://blog.csdn.net/u011068702/article/details/49863931第三步、应该知道…

ArcGis融合小多边形到相邻多边形

&#xfeff;&#xfeff;在有的时候&#xff0c;我们的数据中可能会有许多细小的图斑&#xff0c;这些并不是我们想要的&#xff0c;需要将它们合并到周围的图斑中&#xff0c;如果一个一个手动合并&#xff0c;那工作量之大简直不敢想象。现在借助ArcGIS的Eliminate工具可以很…

如何部署同一个Spring boot web 应用到不同的环境

在现实项目当中我们往往都有不同的部署环境&#xff0c;例如&#xff1a;dev数据库, system test 数据库 和production 数据库&#xff0c; 那么如何把同一个spring boot web app 部署到不同的数据库环境呢&#xff1f;spring boot 提供一个profile的功能&#xff0c; 通过配置…

Oracle bigfile 大文件表空间会影响rman等备份效率

Database 是由一个或多个被称为表空间&#xff08;tablespace&#xff09;的逻辑存储单位构成。表空间内的逻辑存储单位为段&#xff08;segment&#xff09;&#xff0c;段又可以继续划分为数据扩展&#xff08;extent&#xff09;。而数据扩展是由一组连续的数据块&#xff0…

oracle 48小时内_近了近了,内马尔正大步向巴萨走来,西媒称有望48小时内敲定转会...

“即将完成&#xff01;”8月28日的西班牙《每日体育报》给巴萨球迷送上了好消息&#xff0c;称在巴萨高层与大巴黎高层进行最新一轮谈判后&#xff0c;内马尔已经非常接近巴萨了。按照《每日体育报》的说法&#xff0c;巴萨和大巴黎有望在未来24到48小时内就内马尔的转会达成协…

《随机过程》布朗运动理论中的两个反常问题

全世界只有3.14 % 的人关注了爆炸吧知识1827 年&#xff0c;英国植物学家布朗&#xff08;Brown&#xff09;用显微镜观察悬浮在液体中的花粉微粒时&#xff0c;发现花粉微粒总是在做无规则运动。后来人们发现&#xff0c;这是一种广泛存在于自然界、工程技术和社会经济等领域中…

linux之用2张图片描述vim常见命令

对了&#xff0c;使得光标跳转到最后一行是这个命令 G

读《好好学习:个人知识管理精进指南》

关于学习的文章之前写过两篇&#xff1a;《掌握好的学习方法&#xff0c;让你在职场更有竞争力》《程序员是终身学习的职业&#xff0c;应该怎么学习&#xff1f;》我们都是终身学习者&#xff0c;我深知学习的重要性&#xff0c;所以每隔一段时间&#xff0c;有些新的心得和想…