1.2.3 装饰器

对初学者来说,深入理解装饰器是比较困难的,这和大家对Python语言的熟悉程度也有一定的关系。这里初步介绍装饰器的使用。正像其名字一样,装饰器是用来“装饰”的,这里的“装饰”可以理解为“加强”的意思。也就是说,可以通过装饰器来加强我们的程序。装饰器一般用于装饰函数和类,这里仅介绍对函数的装饰。

可能有的读者会想,既然我们想要函数有加强的功能,直接写在函数里面不就行了?当然可以,这确实是一种可行的方法。但是假设想要每个函数打印此函数执行的时间,按照上面的方法,就要在每个函数里面记录开始时间和结束时间,然后计算时间差,再打印出此时间差。这样会使函数变得臃肿,包含太多和函数功能不相关的内容。假设有几十个这样的函数将要多出几十甚至上百行代码来实现这个功能。所以按照常规的方法,把函数“升级”到加强版是十分烦琐的。而装饰器就是化繁为简的法宝。通过装饰器,可以通过简单地定义一个函数,然后在每个函数前多加一行代码就可以实现函数的“升级”,这就是“装饰”。这也是使用装饰器的原因。

先看一个使用装饰器的例子,如下所示。

    import time

    def printtime(func):
        def wrapper(*args, **kwargs):
          print(time.ctime())
          return func(*args, **kwargs)

        return wrapper

    @printtime
    def printhello(name):
        print('Hello', name)

    if __name__ == '__main__':
        printhello('Sam')

运行输出如下。

    Wed Jun 28 01:13:042017
    Hello Sam

这里定义了一个装饰器,用于打印函数开始执行的时间。上面的程序@printtime就是装饰器的关键。下面去掉这句,看看怎样实现同样的功能。在这之前,我们必须知道在Python里面,函数也是对象,也能被当作参数传递,而装饰器的本质就是函数。

    import time

    def printtime(func):
        def wrapper(*args, **kwargs):
          print(time.ctime())
          return func(*args, **kwargs)

        return wrapper

    def printhello(name):
        print('Hello', name)

    if __name__ == '__main__':
        printhello_plus = printtime(printhello)
        printhello_plus('Sam')

其输出同样可以打印时间和问候语。先看以下两句代码。

    printhello_plus = printtime(printhello)
    printhello_plus('Sam')

修改后的代码将printhello函数作为对象传给printtime作为参数,然后printtime函数将返回的wrapper函数赋给了printhello_plus,也就是说此时的printhello_plus函数和wrapper函数是一致的。接下来执行printhello_plus函数,就像wrapper函数中写到的一样,先打印日期时间,再执行一开始传入的函数(此处是printhello)。

也就是说,printhello_plus = printtime(printhello)是对原printhello函数进行“加强”的操作。后面的printhello_plus('Sam')是对加强后函数的调用操作。而@printtime实现的正是这个功能,只是少了这个显式的“加强”并额外赋值的操作。将@printtime置于函数定义之前,就相当于自动进行了“加强”的操作,并且加强版本的函数名还是原来的函数名。

通过这样的拆解,现在对装饰器有了基本认识,但是进一步的学习还需要对闭包等概念有更加深入的认识。在后面爬虫多线程的测试实验中还会用到,与这里的例子是类似的,读者可以尝试着理解。