06.13 Python 装饰器 Decorators

Python 装饰器 Decorators

明白 Python 中的装饰器是Python程序员的一个里程碑。通过这篇文章,你会学习到怎么使用装饰器去写出更有效的代码。

使用装饰器,使你能够扩展函数、方法和类的默认行为,而不需要修改原来的代码。这些行为可能有:

  • 记录日志
  • 添加访问控制授权
  • 计算程序运行时间
  • 控制调用的频率
  • 缓存

为什么要使用装饰器


为什么要使用装饰器呢,这词儿听起来好像很难的样子。除了能给你带来别人顶礼膜拜的表情外,它使你在往 Python 高级程序员的路上越走越近。

假如你的程序有60个方法,老板想让你在一些方法中添加日志或者统计一些信息。怎么办,如果不使用装饰器,你必然要在每个方法里添加代码。

而使用装饰器,只需要在这些方法中的定义上方加入装饰器。 语法格式为:

Python 装饰器 Decorators

在继续深入之前,你需要了解的是Python的函数是第一类对象(first-class object),函数也是一个对象。

  • 函数可以赋值给一个变量,也可以当参数一样传入另一个函数,函数可以返回一个函数对象。
  • 函数里可以定义另一个函数,叫内部函数或者嵌套函数,内部的函数可以访问外面的函数的变量。

装饰器基础


现在看一下什么是装饰器。

装饰器就是包装了一个函数,让你在包装的函数运行之前和之后,执行一些操作。

装饰器让你封装一些可重用的操作,修改函数的默认行为,这样可以不修改之前函数内部的代码。

现在看一下怎么实现一个简单的装饰器,装饰器是可调用的(callable),它接受一个参数是可调用的对象,返回一个可调用的对象。

下面的函数符合装饰器的特性:

Python 装饰器 Decorators

可以看到,do_nothing() 是可调用的(callable),它是一个函数,并且接收一个可调用的参数,然后返回。

让我们给它包装另一个函数:

Python 装饰器 Decorators

在上面的例子中,生成了一个可调用的函数 hi(),它是用 do_nothing()函数包装的另一个函数 hello()。实际执行了 hello() 的代码,输出 hello。

Python提供了 @syntax , 不需要 do_nothing() 函数调用并赋值一个新变量。如下:

Python 装饰器 Decorators

把 @do_nothing 直接放在 hello() 函数的定义上方。

注意 @syntax 语法直接在定义阶段就更改了函数的默认行为,使你很难再访问原来的方法了。因此使用的时候要做好判断,怎么保留和执行之前的函数代码。

装饰器修改函数的默认行为


现在已经了解了装饰器的用法了,那面装饰器是否可以修改原来函数的默认行为呢。看下面的例子。

Python 装饰器 Decorators

输出:

HELLO

看到结果字符串 HELLO,原来的输出小写变成了大写,成功修改了之前函数的输出。

看一下 uppercase() 装饰器,它首先是符合装饰器的特性的,输入一个可调用的函数,返回的是一个闭包函数 newfunc() ,因为 newfunc() 函数位于 uppercase() 函数的里面,所以他可以访问参数 func 。

newfunc() 调用了原来的函数,然后把结果转换为大写。

这个例子体现了,装饰器可以不用修改函数内部的代码,也可以把默认的行为修改。这是很有用的特性,在Python的标准库和第三方库中大量的使用了装饰器。

一个函数上使用多个装饰器


你可以在一个函数上使用多个装饰器。请看下面的例子:

Python 装饰器 Decorators

以上输出:

hello

这个例子在字符串外加了两个HTML标签。可以看到,多个装饰器的应用顺序是从下到上,首先应用 em() 装饰器,而后是 strong() 装饰器,输出结果看 在最外层。

相当于:newfunc = strong(em(hello()))

装饰器函数接收参数


上面的例子,都没有传递参数,那参数怎么从装饰器传入到被装饰的函数上呢?可以使用 Python 的*args 和 *kwargs 特性。下面的语法描述了这种情况:

Python 装饰器 Decorators

使用 *args 和 **kwargs 将装饰器接收到的参数都传入到实际的函数中。

看下面一个简单的例子:

Python 装饰器 Decorators

输出:

Python 装饰器 Decorators

实际的函数 hello(name, age) 成功接收到了参数。

装饰器使用 functools.wraps


当一个函数使用了装饰器后,函数本来的名称,文档字符串(docstring)都被装饰器隐藏了。请看下面例子:

Python 装饰器 Decorators

输出为:

newfunc 

proxy newfunc

hello() 函数加了装饰器后,__name__ 属性变成了 newfunc,__doc__ 文档字符串为 proxy newfunc。它返回的是装饰器内部函数 newfunc 的信息

这样调试程序会比较麻烦,幸好Python标准库为我们提供了装饰器函数 functools.wraps 。你可以在装饰器内部函数上使用它,把被装饰的函数的信息复制到内部函数上。

Python 装饰器 Decorators

输出:

hello
hello someone

成功输出了原始函数的信息。

每次使用装饰器,最好都使用 functools.wraps,这样调试就不会花太多时间。

装饰器总结


  • 装饰器定义了一个可重用的代码块,你可以应用到一个可调用的对象,在调用之前或者之后,修改对象的行为。
  • @syntax 在一个可调用的对象上应用一个装饰器,可以添加多个装饰器,装饰的顺序是由下到上。
  • 为了调试方便,你应该总是使用 functools.wraps 将原始的信息复制到装饰器返回的内部对象上。


分享到:


相關文章: