Python · 装饰器(Decorator)及其应用

Python · 装饰器(Decorator)及其应用

(这里是本章用到的 GitHub 地址)
  • 万物皆对象 —— Python

本章所介绍的装饰器(Decorator)和以后会介绍的元类(Meta Class)都是上面这句话的具现,其中装饰器告诉了我们“函数亦对象”,元类则会告诉我们“类亦对象”

所谓的“函数亦对象” ,意味着函数可以被赋值给变量、通过变量也能调用该函数。举个栗子:

def func(x):
    return x + 1

plus_one = func
print(plus_one(1))
Out[1]:
2

装饰器的核心思想,就是装饰函数这个对象、让函数自身代码不变的情况下、增添一些具有普适性的功能

典型且我们打算进行讲解的一个栗子就是,计算一个函数的运行时间。我们希望做到对任意一个函数,只要我们调用我们的装饰器、就能记录它的耗时、从而可以进一步做性能分析与优化

为此,我们先来看装饰器的基本用法:

@decorator
def func(*args, **kwargs):
    ...

它等价于:

def func(*args, **kwargs):
    ...
func = decorator(func)

其中,decorator 以函数对象为输入参数、并返回一个函数对象。一般来说,它的定义形如:

def decorator(func):
    def wrapper(*args, **kwargs):
        # Do something before function called
        ans = func(*args, **kwargs)
        # Do something after function called
        return ans
    return wrapper

(这里用到了许多 Python 的知识;关于可变参数 *args 和 **kwargs 的说明可以看这里,关于闭包(Closure)可以看这里

从而如果要实现对函数 func 的计时的话,只需再应用标准库 time:

import time

def decorator(func):
    def wrapper(*args, **kwargs):
        t = time.time()
        ans = func(*args, **kwargs)
        t = time.time() - t
        return ans, t
    return wrapper

我们稍微测试一下这个装饰器:

@decorator
def func():
    for _ in range(10 ** 6):
        x = 0
    return "Done"

func()
Out[2]:
('Done', 0.02807450294494629)

Done!

当然,这个简单的版本其实还有许多缺点,包括但不限于:

  • 装饰器本身无法传入参数
  • 函数的 __name__ 属性发生了改变,导致有些依赖函数签名的代码执行会出错

不过 Python 的强大之处就是,当你有一项功能觉得很棘手时、往往已经有现成的库帮你解决了问题。在装饰器这里,wrapt 就是这么一个库。你可以通过:

pip install wrapt

来安装它。其官方教程相当详尽,这里就只说说它的基本用法

上面我们定义的计时 decorator 在使用 wrapt 时会形如:

import time
import wrapt

@wrapt.decorator
def decorator(func, instance, args, kwargs):
    t = time.time()
    ans = func(*args, **kwargs)
    t = time.time() - t
    return ans, t

Done!可以看到少了一层嵌套、从而使代码漂亮不少。同时它几乎解决了所有装饰器可能带来的隐含问题,所以使用 wrapt 来定义装饰器可能总会是一个更好的选择

下面我们来说说怎么给装饰器传参数。正如普通装饰器是返回函数对象的函数,想给装饰器传参的话、就需要定义一个返回装饰器的函数。仍然以 decorator 为例,这次我们打算用一个阈值来判断函数 func 的运行快慢:

import time
import wrapt

def decorator(eps):
    @wrapt.decorator
    def wrapper(func, instance, args, kwargs):
        t = time.time()
        ans = func(*args, **kwargs)
        t = time.time() - t
        if t > eps: print("Slow!")
        else: print("Fast!")
        return ans, t
    return wrapper

我们测试一下我们新的 decorator:

@decorator(0.01)
def func1():
    for _ in range(10 ** 6):
        x = 0
    return "Done"

@decorator(0.05)
def func2():
    for _ in range(10 ** 6):
        x = 0
    return "Done"

func1()
Slow!
Out[3]: 
('Done', 0.028105497360229492)

func2()
Fast!
Out[4]: 
('Done', 0.029077768325805664)

Done!

装饰器还有许多值得挖掘、探讨的地方,不过我打算到此为止、因为以上的功能通常来说已经相当足够。最后放出我在我实现的神经网络里面用到的装饰器的效果来让观众老爷们感受一下装饰器的强大吧(代码可以参见这里):

希望观众老爷们能够喜欢~

编辑于 2017-02-11

文章被以下专栏收录