这几天查装饰器的资料时,意外发现了stackoverflow上一篇很不错的文章,所以就翻译了过来,权当加深自己的理解。
原网址:How to make a chain of function decorators?
是这样的,有人问了一个问题:
能不能用两个装饰器,像这样
@makebold
@makeitalic
def say():
return "Hello"
可以返回这种东西:
"<b><i>Hello</i></b>"
下面是回答:
Decorator基础
- Python的函数都是对象
要理解decorators,首先要明白在Python中,函数都是对象。来看一个简单的例子:
把这一点牢记于心,我们很快就会用到它。
Python的函数有另外一个有趣的特性:它们可以在另一个函数的内部定义!
- 函数引用
你已经知道函数都是对象了。因此,函数
- 可以赋值给一个变量
- 可以在另一个函数内部定义
这意味着一个函数可以返回另一个函数。
这不是全部! 如果你可以返回一个函数,这意味着你也可以把函数作为一个参数。
现在,你已经具备了所有理解decorators所需要的知识了。 不难看出,decorators其实就是“包装纸”,这意味着他们可以让你在它们装饰好的函数前后执行你想要的代码,而不用改动函数本身。
- 自制装饰器
如何手工制作装饰器呢:
现在,你可能希望,每次你调用 a_stand_alone_function 的时候,实际调用的是a_stand_alone_function_decorated 。这其实很容易,只需要用my_shiny_new_decorator 返回的函数来覆盖a_stand_alone_function。
- 揭秘decorators
使用decorator语法来实现前面的例子:
这就是全部了,非常简单。@decorator 其实就是another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
的缩写而已。decorators只是 decorator design pattern的一个变体而已。Python嵌入了几种经典的设计模式来简化开发(比如iterators)。
当然了,decorators是可以累加的:
现在:回答问题的时间到了:
现在你已经可以很轻易地回答这个问题了:
现在,你可以心满意足地离开了,或者再牺牲一些脑细胞来看一下decorators的高级用途。
更深一层的的decorators
- 给被装饰的函数传递参数
- 装饰方法
Python一个有趣的地方在于,方法和函数其实是一样的。唯一的不同是方法的第一个参数是对当前对象的一个引用(self)。
这意味着你也可以用同样的方法来创建一个装饰器!只需记得把 self传入即可:
如果你想要做一个可以通用的decorator —— 一个你可以传递给拥有无论什么参数的函数或者方法的decorator的话,请用 *args, **kwargs 吧:
这意味着你也可以用同样的方法来创建一个装饰器!只需记得把 self传入即可:
- 给decorator传递参数
很好,现在你可能会问:那要怎么给装饰器传递参数?
这可能有点坎坷,因为装饰器只能接受一个函数作为它的参数。
所以你不能直接传递额外的参数给装饰器。
在说出解决方法之前,这里先做一个小提醒:
my_decorator
"@my_decorator
被调用了。所以,当你使用了,你实际上是调用了函数 "my_decorator
" 。看一下最后的大boss: 一点都不令人惊讶。
让我们来做一件本质上一模一样的事情,跳过所有的中间变量: 还可以更简短一些: 看到没有?我们在用 "@" 语法调用一个函数!:)
回到带参数的decorators。如果我们可以用一个函数来即时生成decorator,我们就可以给我们的decorator传递参数了,对吧?
这里就是一个带参数的装饰器了。参数也可以是变量:
没错,你可以像使用这个技巧的任何函数一样把参数传给装饰器。如果你想要,你甚至可以使用*args, **kwargs但是记住,装饰器只被调用一次。就是Python导入脚本的那一刻。之后你没有办法动态地设置参数。当你"import x"的时候,函数已经被装饰了,所以你不能改变任何东西。
实践:装饰一个装饰器
好的,作为一个奖励,我会给你一段代码,使任何装饰器接受任何参数。毕竟,为了能够接受参数,我们使用另一个函数来创建我们的装饰器。我们包装了装饰器。
还有什么其他的我们最近见过的可以用来包装函数的东西吗?
下面就来写一个用来装饰装饰器的装饰器! 它可以这么用: 我知道,这时你最后一次有这种感觉了,就是听了一个人说:“在理解递归之前,你必须首先理解递归。”但是现在,你不觉得掌握了这个真的很爽吗?
最佳做法:
- Python 2.4才引入了装饰器,因此确保你的代码运行在2.4版本以上。
- 装饰器降低了函数调用的速度。记住这一点。
- 你无法撤销一个函数上面的装饰。(有人创建了可以撤销的装饰器,但没人使用它……)所以当一个函数被装饰了,它的所有代码都会被装饰。
- 装饰器包装了函数,这使得他们很难debug。(Python 2.5之后情况好了一些,见下文)
Python 2.5引入了
(有趣的是:functools.wraps()也是一个装饰器!:) )
functools
模块,此模块包含了函数 functools.wraps()
,
这个函数将被装饰的函数的name,mudule,和docstring复制给了它的wrapper。(有趣的是:functools.wraps()也是一个装饰器!:) )
装饰器的用途?
有一个大问题:我能用装饰器做什么?
那有1000种可能性。最典型的用法是扩展一个第三方库的函数(你无法修改这个函数的时候),或者可以用来debug(你不想修改这个函数)。
你可以通过DRY(don't repeat yourself)原则来使用它们扩展几个函数,像这样:
当然,decorators的好处就是你可以立即在几乎任何东西上使用他们而不用重复的写代码。我是说DRY啦。
Python自己提供了几个装饰器:property,staticmethod,等等。
- Django使用装饰器来管理缓存和查看权限。
- 用来伪造内联异步函数调用。
总之就是一片秘密花园!
评论
发表评论