何为装饰器?
装饰器(decorator)是一种语法糖,需要编程语言支持闭包和first-class函数闭包(提供局部变量支持,first-class函数允许将函数作为参数和任意地方创建函数)。装饰器简单来说就是装饰一个函数(对象),在调用之前或者之后附加点操作逻辑,类似下面的逻辑。
|
|
如果没有装饰器语法如何实现相同功能?
如果没有装饰器语法的支持,我们可以用现有的语法实现。例如:
|
|
上面的逻辑比较简单。以wrapped
为参数调用decorator
,返回一个wrapper
函数,再赋值给wrapped
名字。表面看wrapped
没有改变,其实际内容已经被替换成wrapper
这个闭包。有了装饰器语法糖的支持,我们可以简写成:
|
|
python解释器负责调用decorator(wrapped)
再赋值给wrapped
名字的过程。当然上面的例子比较简单,并没有做一些额外的工作,可以稍微修改一下。
|
|
函数如何使用装饰器?
上面的例子已经给出如何使用装饰器的方式,即在被装饰函数的前面加上@decorator
。
|
|
如何使用多装饰器?
python解释器负责从底向上调用装饰器赋值,因此可以写为
|
|
即按照装饰的顺序由下向上调用。类似func = decorator_2(decorator_1(decorator_0(func)))
如何给装饰器转入参数
从前面看,装饰器要求最后能够返回一个函数。这个函数接受唯一的参数func即可。因此实现装饰器传入参数有几种办法。第一种,多层嵌套,装饰器本身接受参数后,再返回一个装饰器即可。当然,为了利用转入装饰器的参数,需要把参数再继续传递下去。
|
|
第二种,利用类实现参数。
|
|
利用类实现实际上是手工编写了一个闭包,再利用__call__
魔术方法将类当做函数运行。
第三种,利用functools.partial 将嵌套扁平化。
|
|
partial叫做函数的柯西化,即将多参数分阶段传入,等待传入最后一个参数才运行的技术。
|
|
装饰器的优点?
可以不改变函数名称和参数的情况下实现额外的功能。因为可以在不改变原有逻辑和代码的情况下附加功能,可以实现相同前置/后置逻辑的抽象。
python本身利用装饰器语法实现了很多功能。例如: @classmethod 实现类方法,@staticmethod实现类的静态方法。
|
|
还有例如用 @property 装饰器将类方法转成类属性(如上所示)等。编程中可以将所有函数均要做的逻辑抽象为一个装饰器,以此避免代码的重复。例如django中的@login_required
函数用来在调用view之前先验证当前请求的用户是否登录。
装饰器的缺点?
装饰器也有缺点,例如上例中的wrapped.__name__
返回的并不是wrapped
这个名字而是wrapper
。这是因为wrapped
本身被替换成了一个wrapper
闭包,所以返回的是这个闭包的各种属性,可以通过wrapped.func_closure
等func_xx函数查看。这叫做装饰器的副作用(side affect)。因此如果原来调用wrapped的逻辑,需要用到这些属性(包括__doc__
,__name__
,__module__
等),那么这些逻辑就会出错。
如何解决装饰器的副作用?
明白了装饰器的副作用的原因,我们就可以手动解决一下。将闭包的属性赋值为被装饰的函数的属性即可。
|
|
在装饰器中直接将装饰器的属性改为被装饰的对象的属性即可。
仔细看可以见到这种技巧的本质,是在装饰器中的wrapper
函数之后干了点事情,当然可以写一个通用的装饰wrapper
的装饰器来代替手工赋值。最后实现的类似这样:
|
|
利用一个装饰器来完成wrapper.__name__ = func.__name__
的工作。因为需要func的属性值,因此这个装饰器需要传入func参数。
|
|
可以看见最终wraps
函数实现了functools.wraps
的功能。
当然wraps
有两点不够好:第一点是两层的_inner
和inner
看起来比较难于理解;第二点,如果被装饰对象是一个类,那么类的属性将丢失。对于第一点,通过利用functools.partial将多层嵌套扁平化。对于第二点比较好解决,将类的__dict__
属性同样赋值给闭包即可,因此增加updated参数实现要将哪些属性更新。
|
|
这个wraps
就是函数functools.wraps
的实现,与传统的写法的不同点是将内部的update_wrapper
写在了外面。wraps
函数返回了一个这样的函数update_wrapper
,这个函数的一些参数都已经给定,只剩下wrapper
等待传入。
另外需要说明的是,partial函数是由C语言低层实现,不会修改update_wrapper的各种属性。