Pulpcode

捕获,搅碎,拼接,吞咽

0%

聊聊python装饰器

前言

实际上关于python装饰器,有很多可以写的,我这里仅仅对一些关键的概念进行梳理。

我喜欢跟人说,装饰器类似于在你执行一件事情(函数调用的时候)的前方做一些事儿,在后方做一些事儿。

我这个说法也许更应该算是解释,装饰器到底在干什么。实际上装饰器就是一个语法糖。

闭包与装饰器

什么是闭包?能够捕获状态的函数或者方法就叫做闭包。

得力与python的函数是“一阶公民”,使得函数可以被当成一个对象被赋值,被当做函数参数,在函数中定义一个函数并返回。

外层函数为内层函数创建了一个上下文,当内层函数返回后,它就有一个属于自己的上下文环境。这就是它能保存的状态。这个功能用来教学的话,经常是验证这个函数被调用了几次的例子。

你仔细想一下,能够生成函数的函数,将其称之为函数工厂,我们通过传入的参数,让其生成我们想要的函数,这带来了灵活性和可定制型。

1
2
3
4
def addn(n):
def wrapper(m):
return n+m
return wrapper

我们再来说说函数一阶公民,如果一个函数a被当成参数传入,我们在其内部定义另一个函数b,b函数在调用a的同时,做了些别的事情。这样的话a函数就把a函数“包装”了。

1
2
3
@decorator
def function()
pass

其实这等价于:

1
function = decorator(function)

我觉得装饰器的好处之一是你不用修改源代码。比如你在很多地方都调用了这个函数,现在仅仅在函数的定义部分,给它“装饰”一下,就可以添加新的功能。

而且装饰器本身的命名,也为代码带来了一定的可读性,这使得装饰器更像是一种注解。不过我觉得做的不好的是tornado的这个装饰器,它的命名就带来很大的误导性,不过还是要怪那些人不看文档。

不过你纳闷这和闭包这个概念有什么关系?因为装饰器和闭包,就可以实现带有参数的装饰器。

带有参数的装饰器

之前的段落提到了带有参数的装饰器。你会发现带有参数的装饰器,它实际上有两层,外面的那一层,就是一个闭包,而里面那一层,才定义了一个装饰器函数,这也就是所谓的二级封装。
大概的写法是这样的:

1
2
3
4
5
6
def aaa(arg1, arg2):
def _wrapper(func):
def __wrapper(*args, **kw):
return func(*args, **kw)
return __wrapper
return _wrapper

就像刚才那个例子。你可以想想,带参数的装饰器,实际上等价于

1
2
3
@decorator(*args, **kwargs)
def function()
pass
1
function = decorator(*args, **kwargs)(function)

参数带来的一个小细节

你会发现,作为参数,就可以直接当作闭包,完全不需要:

1
2
3
4
5
6
def a(b):
c = b
def wrapper(func):
c....
func()
return wrapper()

你要理解python中的变量就是内存块,变量名只不过是一个标签而已。不过这个b如果要当作一个容器,那就要注意它的默认值一定要设置为b=None,而不是b=[],你可以打印一下这个函数的内建属性:func_defaults,就明白我说的是什么了。

一些工具

使用python的functools.wraps可以不让函数的__name__,和__doc__属性被替换。

因为你“包装”了一个函数之后,它其实已经是另一个函数了,所以函数的一些自省的属性也就丢失了,functools.wraps就可以帮助你解决这个问题。

装饰器与装饰器模式

相似的东西,总会拿来做对比,更有趣的是,即使这两个东西仅仅是名字相同,
比如java和javascrip,再比如重写和重载这两个其实根本没啥关系的东西,也常拿出来做比较。当然,装饰器和装饰器模式还是很有关系的。

对于设计模式中的装饰器模式,可以在不用改变原类文件和原用继承的情况下,动态的扩展一个对象的功能,通过一个包装对象,也就是装饰器来包裹一个真实的对象。那么你可以说,你的原函数是独立于装饰函数。装饰函数,仅仅在做扩展而已。这也就是aop面向切面编程。

实际上python的装饰器,就是使python在语法层面上就支持装饰器模式,也就我刚才所说的,装饰器可以在一个函数调用前做一些事情,在调用后做一些事情。

我甚至理解,装饰器实际上类似于宏,在做一件用代码生成代码的事情,当然,这本身是不对的,我仅仅是想想而已,它却是仅仅是函数嵌套调用的关系,而非真正的代码级别的替换。

实用主义

我喜欢做一个实用主义,所以每次看到一些讲装饰器的原理和能干什么的时候,我就想问一句,那么我能用它来做什么呢?接下来我就带你去看看,装饰器散布在python编程的哪些方面。

类定义

1
2
3
4
5
6
7
8
9

class Example(object):
@classmethod
def foo(cls):
print "CLASS: %s" % cls

@staticmethod
def bar():
print "static method"

在python中,经常会看到这样的定义,声明这是一个类方法,那是一个静态方法,
我起初一直不能理解类方法与静态方法有什么区别,现在我只能说,类方法算是一个绑定在类这个对象上的方法,而静态方法,就是简单的封装到类的命名空间的方法。与类无关系。

还有property这样的装饰器,使你的函数看起来像是一个属性,你在c#或者java中建模的时候,经常会用到这个方法来建模,写一些被称为是访问器的东西。

web框架

flask里面的路由就用到了装饰器,这就是我之前提到的带有参数的装饰器。

1
2
3
@app.route('/')
def index():
return render_template('home.html')

tornado中的异步也用到了装饰器

1
2
@asynchronous
@gen.coroutine

第一个装饰器是让连接变成长连接,第二个装饰器能够使得框架和你的yield进行通讯,这实际上算是修改调用时的上下文,这个如果不理解yield将非常苦涩难懂。

之前我写过一个验证用户登陆才能显示此页面的代码,使得这个handler必须要有xx的权限,才能够登录,装饰器使得,这个验证功能就像是“贴”上去。

缓存设计

这里先写一个python的斐波那契数列,

1
2
3
def fib(n):
if n <= 0: return 1
return fib(n - 1) + fib(n - 2)

你知道对于斐波那契数列,这种树形递归的程序,实际上它进行了大量的重复计算。

那么我可以用一个memory(缓存)来将那些已经计算过的值写入到memory中。

1
2
3
4
5
6
7
8
9
memory = {}

def fib(n):
if n <= 0: return 1
if n in memory:
return memory[n]
r = fib(n-1) + fib(n-2)
memory[n] = r
return r

当然这个时候,我们可以做一个装饰器。

1
2
3
4
5
6
7
8
9
def memorized(func):
memory = {}
def wrapper(n):
if n in memory:
return memory[n]
r = func(n-1) + func(n-2)
memory[n] = r
return r
return wrapper
1
2
3
4
@memorized
def fib(n):
if n <= 0: return 1
return fib(n - 1) + fib(n - 2)

这里有一个python的wiki介绍了python装饰器的各种妙用。https://wiki.python.org/moin/PythonDecoratorLibrary