Python装饰器
2015-11-30 20:44
826 查看
这段时间因为工作忙,已经很久没更新博客了。前几天看到一个开源项目,使用django框架,后台用Python,前台用JavaScript。看后台源码时,总是遇到@什么什么。因为以前算是系统的学过Python,所以知道这是Python的装饰器,但对这一知识点的理解还是自认不够,看代码还是比较吃力。今天决定写这个博客,一来是梳理一下自己头脑中的这一知识点,二来是做个记录,便于日后查阅,三来是给跟我一样对这一知识点理解不透彻的人一点帮助。
Python装饰器实际上是一个Python函数,参数是一个函数,返回值还是一个函数。先来看装饰器这三个字,顾名思义,它就是用来装饰别人的,那么装饰谁呢?被装饰的对象就放在参数中,那么装饰后的结果呢?装饰后的结果就用返回值返回。
来看一个简单的例子:
现在我想做这样一件事情,如果某个函数有函数说明,我们就把这个函数的函数说明打印出来;如果没有,就打印函数名 + ‘has no doc’这句话。并且提出一个最基本要求,就是函数的定义以及函数的调用方式必须保持不变。这里为什么要提这个要求,因为如果函数的调用方式改变的话,那么你就要改变每一处调用该函数的代码,如果调用该函数的地方多了(一个文件还好,跨文件就比较麻烦了),工作量可想而知。
这个时候装饰器就能派上用场了。
我们可以这样改:
上面的decorator函数就是我们定义的装饰器,参数是func,就是要装饰的函数,返回值是装饰后的函数。我们只是定义了一个装饰器函数,并用该装饰器装饰了funcA和funcB,函数funcA和函数funcB的定义和调用方式我们没做任何改变。这样就满足了需求,不过也有一个问题,那就是上面这个装饰器的局限性很大,比如我这里定义一个函数funcC,如下:
这个时候就不能用decorator来装饰funcC(不信可以去试一下),为什么?最直观的解释就是被装饰的函数有三个参数,而装饰后的函数无参数,破坏了一致性。这就好像一个人去理发店,理发前和理发后应该还是同一个人,不能说去了一趟理发店,这个人就不是这个人了。还可以这样解释,程序是顺序执行的,它会按照代码一行一行的执行。当你用
那么我想让decorator既可以装饰funcA和funcB,又可以装饰funcC,怎么办呢?刚才出错的原因就是装饰后的函数的参数与装饰前的函数的参数匹配不上,看到这,有经验的程序员里面想到了解决方案:*args, **kwargs。没错,就是它了,它可以匹配任意参数。不管装饰前你的参数是什么样子,不管你有几个参数,1个?2个?3个?(i don’t care.)我都用这个来匹配。代码如下:
注意在wrap函数内,函数调用也改成了func(*args, **kwargs)。
在上面我们可以发现一个共同点,如:
每次这样写显得比较麻烦,我们可以简写成下面这样:
其实上面两种写法的效果完全一样。只是下面那种写法显得简洁,可以显得你更牛X。不要以为@有什么好神秘的,其实在生活中我们不是经常用@吗?我们呼叫某个人时,或者评论某个人的说说时,就会用到@啊!@decorator,你可以把它理解为,“喂,decorator,你帮我装饰一下下面定义的这个函数”。好了,知道了这个,我们可以把代码修改成下面这样:
再来个更复杂一点的,我们可以对装饰器decorator再来一层封装,如下面这样:
我们在最外层定义了一个函数decorator_outer,它有一个参数par,这个函数返回我们以前的那个装饰器函数。这个decorator_outer就是新的装饰器了。
然后下面也要改成这样:
完整的代码如下:
这样的装饰器,我们把它称为“带参数的装饰器”。这个也好理解,它其实就是对旧的装饰器的一种封装而已。
原文地址:http://tanlian.co/2015/11/26/Python%E8%A3%85%E9%A5%B0%E5%99%A8/
Python装饰器实际上是一个Python函数,参数是一个函数,返回值还是一个函数。先来看装饰器这三个字,顾名思义,它就是用来装饰别人的,那么装饰谁呢?被装饰的对象就放在参数中,那么装饰后的结果呢?装饰后的结果就用返回值返回。
来看一个简单的例子:
def funcA(): print "in funcA." def funcB(): '''This is funcB.''' print "in funcB." funcA() funcB()
现在我想做这样一件事情,如果某个函数有函数说明,我们就把这个函数的函数说明打印出来;如果没有,就打印函数名 + ‘has no doc’这句话。并且提出一个最基本要求,就是函数的定义以及函数的调用方式必须保持不变。这里为什么要提这个要求,因为如果函数的调用方式改变的话,那么你就要改变每一处调用该函数的代码,如果调用该函数的地方多了(一个文件还好,跨文件就比较麻烦了),工作量可想而知。
这个时候装饰器就能派上用场了。
我们可以这样改:
def decorator(func): def wrap(): if func.__doc__ == None: print func.__name__ + " has no doc." else: print func.__name__ + "'s doc is " + func.__doc__ func() return wrap def funcA(): print "in func." funcA = decorator(funcA) def funcB(): '''This is funcB.''' print "in funcB." funcB = decorator(funcB) funcA() funcB()
上面的decorator函数就是我们定义的装饰器,参数是func,就是要装饰的函数,返回值是装饰后的函数。我们只是定义了一个装饰器函数,并用该装饰器装饰了funcA和funcB,函数funcA和函数funcB的定义和调用方式我们没做任何改变。这样就满足了需求,不过也有一个问题,那就是上面这个装饰器的局限性很大,比如我这里定义一个函数funcC,如下:
def funcC(par1, par2, par3): '''This is funcC ''' print str(par1) + str(par2) + str(par3)
这个时候就不能用decorator来装饰funcC(不信可以去试一下),为什么?最直观的解释就是被装饰的函数有三个参数,而装饰后的函数无参数,破坏了一致性。这就好像一个人去理发店,理发前和理发后应该还是同一个人,不能说去了一趟理发店,这个人就不是这个人了。还可以这样解释,程序是顺序执行的,它会按照代码一行一行的执行。当你用
funcC = decorator(funcC)来装饰funcC后,这个时候funcC就不再是funcC了,而是函数wrap,而函数wrap是无参数的,所以当你调用funcC(1, 2, 3)的时候会报错。错误信息类似下面这样:
那么我想让decorator既可以装饰funcA和funcB,又可以装饰funcC,怎么办呢?刚才出错的原因就是装饰后的函数的参数与装饰前的函数的参数匹配不上,看到这,有经验的程序员里面想到了解决方案:*args, **kwargs。没错,就是它了,它可以匹配任意参数。不管装饰前你的参数是什么样子,不管你有几个参数,1个?2个?3个?(i don’t care.)我都用这个来匹配。代码如下:
def decorator(func):
def wrap(*args, **kwargs):
if func.__doc__ == None:
print func.__name__ + " has no doc."
else:
print func.__name__ + "'s doc is " + func.__doc__
func(*args, **kwargs)
return wrap
def funcA():
print "in func."
funcA = decorator(funcA)
def funcB():
'''This is funcB.'''
print "in funcB."
funcB = decorator(funcB)
def funcC(par1, par2, par3): '''This is funcC ''' print str(par1) + str(par2) + str(par3)
funcC = decorator(funcC)
funcA()
funcB()
funcC(1, 2, 3)
注意在wrap函数内,函数调用也改成了func(*args, **kwargs)。
在上面我们可以发现一个共同点,如:
def func(...): ... func = decorator(func)
每次这样写显得比较麻烦,我们可以简写成下面这样:
@decorator def func(...): ...
其实上面两种写法的效果完全一样。只是下面那种写法显得简洁,可以显得你更牛X。不要以为@有什么好神秘的,其实在生活中我们不是经常用@吗?我们呼叫某个人时,或者评论某个人的说说时,就会用到@啊!@decorator,你可以把它理解为,“喂,decorator,你帮我装饰一下下面定义的这个函数”。好了,知道了这个,我们可以把代码修改成下面这样:
def decorator(func):
def wrap(*args, **kwargs):
if func.__doc__ == None:
print func.__name__ + " has no doc."
else:
print func.__name__ + "'s doc is " + func.__doc__
func(*args, **kwargs)
return wrap
@decorator
def funcA():
print "in func."
@decorator
def funcB():
'''This is funcB.'''
print "in funcB."
@decorator
def funcC(par1, par2, par3): '''This is funcC ''' print str(par1) + str(par2) + str(par3)
funcA()
funcB()
funcC(1, 2, 3)
再来个更复杂一点的,我们可以对装饰器decorator再来一层封装,如下面这样:
def decorator_outer(par): def decorator(func): def wrap(*args, **kwargs): if func.__doc__ == None: print func.__name__ + " has no doc." else: print func.__name__ + "'s doc is " + func.__doc__ print par func(*args, **kwargs) return wrap return decorator
我们在最外层定义了一个函数decorator_outer,它有一个参数par,这个函数返回我们以前的那个装饰器函数。这个decorator_outer就是新的装饰器了。
然后下面也要改成这样:
@decorator_outer("haha") def funcA():
完整的代码如下:
def decorator_outer(par):
def decorator(func):
def wrap(*args, **kwargs):
if func.__doc__ == None:
print func.__name__ + " has no doc."
else:
print func.__name__ + "'s doc is " + func.__doc__
print par
func(*args, **kwargs)
return wrap
return decorator
@decorator_outer("haha") def funcA():
print "in func."
@decorator_outer("hehe")
def funcB():
'''This is funcB.'''
print "in funcB."
@decorator_outer("hoho")
def funcC(par1, par2, par3): '''This is funcC ''' print str(par1) + str(par2) + str(par3)
funcA()
funcB()
funcC(1, 2, 3)
这样的装饰器,我们把它称为“带参数的装饰器”。这个也好理解,它其实就是对旧的装饰器的一种封装而已。
原文地址:http://tanlian.co/2015/11/26/Python%E8%A3%85%E9%A5%B0%E5%99%A8/
相关文章推荐
- python基础_数据类型
- [LeetCode]题解(python):063-Unique Paths II
- python range范围
- Python3 基本数据类型注意事项
- [LeetCode]题解(python):062-Unique Paths
- Python爬取豆瓣电影top250
- leetcode之Remove Duplicates from Sorted List
- Ubuntu 14.04下OpenCV 3.0+Python 2.7安装测试
- python题目:一个小小的猜名有戏
- python 3 UDP小例子
- 关于 python 的 @property总结和思考
- Python批量修改文件名-后缀
- python oop __slots__方法
- [转载]Python注册表信息丢失的解决方案
- Python之封装diff命令的项目比较命令(格式化diff输出结果)
- 第一个python程序
- python学习系列之python装饰器基础(5)---多装饰器的使用
- Python常见问题和技巧
- Python+Selenium自动化
- python subprocess,ConfigParser