前言
实际上关于python装饰器,有很多可以写的,我这里仅仅对一些关键的概念进行梳理。
我喜欢跟人说,装饰器类似于在你执行一件事情(函数调用的时候)的前方做一些事儿,在后方做一些事儿。
我这个说法也许更应该算是解释,装饰器到底在干什么。实际上装饰器就是一个语法糖。
闭包与装饰器
什么是闭包?能够捕获状态的函数或者方法就叫做闭包。
得力与python的函数是“一阶公民”,使得函数可以被当成一个对象被赋值,被当做函数参数,在函数中定义一个函数并返回。
外层函数为内层函数创建了一个上下文,当内层函数返回后,它就有一个属于自己的上下文环境。这就是它能保存的状态。这个功能用来教学的话,经常是验证这个函数被调用了几次的例子。
你仔细想一下,能够生成函数的函数,将其称之为函数工厂,我们通过传入的参数,让其生成我们想要的函数,这带来了灵活性和可定制型。
1 | def addn(n): |
我们再来说说函数一阶公民,如果一个函数a被当成参数传入,我们在其内部定义另一个函数b,b函数在调用a的同时,做了些别的事情。这样的话a函数就把a函数“包装”了。
1 |
|
其实这等价于:
1 | function = decorator(function) |
我觉得装饰器的好处之一是你不用修改源代码。比如你在很多地方都调用了这个函数,现在仅仅在函数的定义部分,给它“装饰”一下,就可以添加新的功能。
而且装饰器本身的命名,也为代码带来了一定的可读性,这使得装饰器更像是一种注解。不过我觉得做的不好的是tornado的这个装饰器,它的命名就带来很大的误导性,不过还是要怪那些人不看文档。
不过你纳闷这和闭包这个概念有什么关系?因为装饰器和闭包,就可以实现带有参数的装饰器。
带有参数的装饰器
之前的段落提到了带有参数的装饰器。你会发现带有参数的装饰器,它实际上有两层,外面的那一层,就是一个闭包,而里面那一层,才定义了一个装饰器函数,这也就是所谓的二级封装。
大概的写法是这样的:
1 | def aaa(arg1, arg2): |
就像刚才那个例子。你可以想想,带参数的装饰器,实际上等价于
1 |
|
1 | function = decorator(*args, **kwargs)(function) |
参数带来的一个小细节
你会发现,作为参数,就可以直接当作闭包,完全不需要:
1 | def a(b): |
你要理解python中的变量就是内存块,变量名只不过是一个标签而已。不过这个b如果要当作一个容器,那就要注意它的默认值一定要设置为b=None,而不是b=[],你可以打印一下这个函数的内建属性:func_defaults,就明白我说的是什么了。
一些工具
使用python的functools.wraps可以不让函数的__name__,和__doc__属性被替换。
因为你“包装”了一个函数之后,它其实已经是另一个函数了,所以函数的一些自省的属性也就丢失了,functools.wraps就可以帮助你解决这个问题。
装饰器与装饰器模式
相似的东西,总会拿来做对比,更有趣的是,即使这两个东西仅仅是名字相同,
比如java和javascrip,再比如重写和重载这两个其实根本没啥关系的东西,也常拿出来做比较。当然,装饰器和装饰器模式还是很有关系的。
对于设计模式中的装饰器模式,可以在不用改变原类文件和原用继承的情况下,动态的扩展一个对象的功能,通过一个包装对象,也就是装饰器来包裹一个真实的对象。那么你可以说,你的原函数是独立于装饰函数。装饰函数,仅仅在做扩展而已。这也就是aop面向切面编程。
实际上python的装饰器,就是使python在语法层面上就支持装饰器模式,也就我刚才所说的,装饰器可以在一个函数调用前做一些事情,在调用后做一些事情。
我甚至理解,装饰器实际上类似于宏,在做一件用代码生成代码的事情,当然,这本身是不对的,我仅仅是想想而已,它却是仅仅是函数嵌套调用的关系,而非真正的代码级别的替换。
实用主义
我喜欢做一个实用主义,所以每次看到一些讲装饰器的原理和能干什么的时候,我就想问一句,那么我能用它来做什么呢?接下来我就带你去看看,装饰器散布在python编程的哪些方面。
类定义
1 |
|
在python中,经常会看到这样的定义,声明这是一个类方法,那是一个静态方法,
我起初一直不能理解类方法与静态方法有什么区别,现在我只能说,类方法算是一个绑定在类这个对象上的方法,而静态方法,就是简单的封装到类的命名空间的方法。与类无关系。
还有property这样的装饰器,使你的函数看起来像是一个属性,你在c#或者java中建模的时候,经常会用到这个方法来建模,写一些被称为是访问器的东西。
web框架
flask里面的路由就用到了装饰器,这就是我之前提到的带有参数的装饰器。
1 |
|
tornado中的异步也用到了装饰器
1 |
第一个装饰器是让连接变成长连接,第二个装饰器能够使得框架和你的yield进行通讯,这实际上算是修改调用时的上下文,这个如果不理解yield将非常苦涩难懂。
之前我写过一个验证用户登陆才能显示此页面的代码,使得这个handler必须要有xx的权限,才能够登录,装饰器使得,这个验证功能就像是“贴”上去。
缓存设计
这里先写一个python的斐波那契数列,
1 | def fib(n): |
你知道对于斐波那契数列,这种树形递归的程序,实际上它进行了大量的重复计算。
那么我可以用一个memory(缓存)来将那些已经计算过的值写入到memory中。
1 | memory = {} |
当然这个时候,我们可以做一个装饰器。
1 | def memorized(func): |
1 |
|
这里有一个python的wiki介绍了python装饰器的各种妙用。https://wiki.python.org/moin/PythonDecoratorLibrary