明白 Python 中的装饰器是Python程序员的一个里程碑。通过这篇文章,你会学习到怎么使用装饰器去写出更有效的代码。
使用装饰器,使你能够扩展函数、方法和类的默认行为,而不需要修改原来的代码。这些行为可能有:
- 记录日志
- 添加访问控制授权
- 计算程序运行时间
- 控制调用的频率
- 缓存
为什么要使用装饰器
为什么要使用装饰器呢,这词儿听起来好像很难的样子。除了能给你带来别人顶礼膜拜的表情外,它使你在往 Python 高级程序员的路上越走越近。
假如你的程序有60个方法,老板想让你在一些方法中添加日志或者统计一些信息。怎么办,如果不使用装饰器,你必然要在每个方法里添加代码。
而使用装饰器,只需要在这些方法中的定义上方加入装饰器。 语法格式为:在继续深入之前,你需要了解的是Python的函数是第一类对象(first-class object),函数也是一个对象。
- 函数可以赋值给一个变量,也可以当参数一样传入另一个函数,函数可以返回一个函数对象。
- 函数里可以定义另一个函数,叫内部函数或者嵌套函数,内部的函数可以访问外面的函数的变量。
装饰器基础
现在看一下什么是装饰器。
装饰器就是包装了一个函数,让你在包装的函数运行之前和之后,执行一些操作。
装饰器让你封装一些可重用的操作,修改函数的默认行为,这样可以不修改之前函数内部的代码。
现在看一下怎么实现一个简单的装饰器,装饰器是可调用的(callable),它接受一个参数是可调用的对象,返回一个可调用的对象。
下面的函数符合装饰器的特性:
可以看到,do_nothing() 是可调用的(callable),它是一个函数,并且接收一个可调用的参数,然后返回。
让我们给它包装另一个函数:
在上面的例子中,生成了一个可调用的函数 hi(),它是用 do_nothing()函数包装的另一个函数 hello()。实际执行了 hello() 的代码,输出 hello。
Python提供了 @syntax , 不需要 do_nothing() 函数调用并赋值一个新变量。如下:
把 @do_nothing 直接放在 hello() 函数的定义上方。
注意 @syntax 语法直接在定义阶段就更改了函数的默认行为,使你很难再访问原来的方法了。因此使用的时候要做好判断,怎么保留和执行之前的函数代码。
装饰器修改函数的默认行为
现在已经了解了装饰器的用法了,那面装饰器是否可以修改原来函数的默认行为呢。看下面的例子。
输出:
HELLO
看到结果字符串 HELLO,原来的输出小写变成了大写,成功修改了之前函数的输出。
看一下 uppercase() 装饰器,它首先是符合装饰器的特性的,输入一个可调用的函数,返回的是一个闭包函数 newfunc() ,因为 newfunc() 函数位于 uppercase() 函数的里面,所以他可以访问参数 func 。
newfunc() 调用了原来的函数,然后把结果转换为大写。
这个例子体现了,装饰器可以不用修改函数内部的代码,也可以把默认的行为修改。这是很有用的特性,在Python的标准库和第三方库中大量的使用了装饰器。
一个函数上使用多个装饰器
你可以在一个函数上使用多个装饰器。请看下面的例子:
以上输出:
hello
这个例子在字符串外加了两个HTML标签。可以看到,多个装饰器的应用顺序是从下到上,首先应用 em() 装饰器,而后是 strong() 装饰器,输出结果看 在最外层。
相当于:newfunc = strong(em(hello()))
装饰器函数接收参数
上面的例子,都没有传递参数,那参数怎么从装饰器传入到被装饰的函数上呢?可以使用 Python 的*args 和 *kwargs 特性。下面的语法描述了这种情况:
使用 *args 和 **kwargs 将装饰器接收到的参数都传入到实际的函数中。
看下面一个简单的例子:
输出:
实际的函数 hello(name, age) 成功接收到了参数。
装饰器使用 functools.wraps
当一个函数使用了装饰器后,函数本来的名称,文档字符串(docstring)都被装饰器隐藏了。请看下面例子:
输出为:
newfunc
proxy newfunc
hello() 函数加了装饰器后,__name__ 属性变成了 newfunc,__doc__ 文档字符串为 proxy newfunc。它返回的是装饰器内部函数 newfunc 的信息。
这样调试程序会比较麻烦,幸好Python标准库为我们提供了装饰器函数 functools.wraps 。你可以在装饰器内部函数上使用它,把被装饰的函数的信息复制到内部函数上。
输出:
hello
hello someone
成功输出了原始函数的信息。
每次使用装饰器,最好都使用 functools.wraps,这样调试就不会花太多时间。
装饰器总结
- 装饰器定义了一个可重用的代码块,你可以应用到一个可调用的对象,在调用之前或者之后,修改对象的行为。
- @syntax 在一个可调用的对象上应用一个装饰器,可以添加多个装饰器,装饰的顺序是由下到上。
- 为了调试方便,你应该总是使用 functools.wraps 将原始的信息复制到装饰器返回的内部对象上。
閱讀更多 趣喜歡編程 的文章