python装饰器
作为python中一个比较重要也比较高级的语法功能,关于装饰器的博客和文章在网上较多.看过不少相关的博客和书,每一次看完都觉得自己懂了,原来是这样,挺简单的嘛.到实际要写的时候,发现还是忘记的差不多了,也就是没有弄懂.最近写装饰器的时候,遇到一些疑惑,就准备记录下来,以免下次再有同样的疑惑时能够有迹可寻.
需求: 写一个装饰器,装饰器的参数作为字典的键,被装饰函数为字典的值,如:
@deco('/index') def index(): ... maps = {'/index': index}
我的代码:
maps = {} def deco(path): def wrap1(func): def wrap2(*args, **kwargs): maps[path] = func return func return wrap2 return wrap1 @deco('/index') def index(): return 'I am index' print maps
按照预期,打印maps应该是:
{'/index', <function index at ....>}
结果为空.
为什么?我突然想到flask源码中添加路由的装饰器的写法,先写一个添加函数,再在基础之上写一个装饰器.于是,我就立马动手了.
def add_address(path, func): maps[path] = func def deco(path): def wrap(func): add_address(path, func) return func return wrap
这次发现有了.
为什么两种写法结果不一致?第一种写法具有三层结构,第二种写法也是三层结构(deco+wrap+add_address).仔细看了下,发现第二种写法其实只有两层结构,add_address函数被调用了,相当于一个结果.两种方法的差别有了,问题肯定在两者的差别之中.
从装饰器的本质开始,先分析第二种写法.
@deco('/index') def index(): ...
等价于
index = deco('/index')(index)
回溯至装饰器中,相当于调用了wrap()
index = wrap(index)
接着:
调用了add_address函数,并返回index函数
没有问题
再看看第一种方法:
@deco('/index') def index(): ...
等价于
index = deco('/index')(index)
相当于调用wrap1(index)
index = wrap1(index)
返回的是wrap2,即
index = wrap2
自此,装饰器完成了自己的工作,对于wrap2函数并没有调用.因此,也就没有对maps字典进行添加操作.
对第一种方法进行修改:
(1)去掉wrap2函数,变成第二种方法
def deco(path): def wrap1(func): maps[path] = func return func return wrap1
(2)将maps[path] = func提升一级
def deco(path): def wrap1(func): maps[path] = func def wrap2(*args, **kwargs): return func(*args, **kwargs) # 一定要把(*args, **kwargs)加上,否则在调用index函数时,返回的是index函数,而不是调用index函数的结果 return wrap2
return wrap1
接着我又重头捋了一下装饰器的内容
(1)从一个简单的装饰器开始
def deco(func): print 'I am deco' return func @deco def foo(): print 'I am foo'
等价于
foo = deco(foo)
运行结果:
I am deco # 说明装饰器加载时,就已经运行了
(2)被装饰函数(如foo函数)有参数
在(1)中的装饰器也是可以用的,因为装饰器装饰之后返回的就是原函数,并没有调用
foo = deco(foo) = foo
对foo函数进行调用时,才会涉及参数
foo(2) = deco(foo)(2) = foo(2)
还有一种写法,就是在包裹一层函数
def deco(func): def wrap(*args, **kwarags): print 'I am wrap' return func(*args, **kwargs) return wrap
等价于:
foo = deco(foo) = wrap
调用foo时:
foo(2) = deco(foo)(2) = wrap(2) = func(2) = foo(2) # 此时才会打印'I am wrap'
在有一层函数包裹时,被装饰函数一定得调用最终才能顺利返回原函数想要的结果(整体上,函数被包裹了几层,就得调用几次)
这两种写法不同点在第一种返回的是原来的函数,第二种函数的函数名改变成了wrap,结果没有改变(可以了解一下functools.wraps和functool.update_wrapper)
链接: https://docs.python.org/2/library/functools.html#functools.update_wrapper
(3)装饰函数自带参数
和前面讨论的问题差不多