Python进阶话题杂谈(十四)装饰器

装饰器是Python中最难理解的语法之一,但较之其他冷门语法又相对较常用。但有必要指出的是,这里所说的较常用,指的是Python自带的一些装饰器,如@property、@abstractmethod等,自定义装饰器在实际中是较少使用的。

1 高阶函数

函数,作为一段代码的抽象,其本质上也对应于一个表示函数代码起点的内存地址。每当调用一个函数时,程序挂起并跳转到函数所在的地址开始执行,结束后再退回到调用点,继续执行接下来的语句。由此可见,就数据结构而言,函数与数组的原理类似,可以用一个指针来保存函数的起始位置。故在C语言中有“函数指针”类型,且由于函数可以用指针来传递,函数指针就可以作为另一个函数的参数,从而出现了“以函数作为参数的函数”,这样的函数就称为高阶函数。

在Python中,由于对象引用的存在,函数也同样作为一类对象(函数对象),可以通过其引用传递,故在Python中将函数作为参数传递,与其他数据结构的传递是一样的。Python中就有一些常用的高阶函数,如sort、max、min等带有key参数的函数,map、reduce、filter这样的带有函数参数的函数等等。这样的函数都以函数作为参数,并在函数内部调用传入的函数参数。

2 无参装饰器

Python的装饰器被定义为一个高阶函数,这个高阶函数接受一个函数作为其唯一函数,并返回另一个函数作为其唯一返回值,这样的特殊函数就称为装饰器。装饰器函数内部应包含一个完整的函数定义过程,并将这个新的函数作为返回值返回。在此函数内部应包含原函数的调用语句,以及其他额外添加的语句。装饰器定义完成后,就可以使用“@”符号对其他函数进行装饰。

当使用装饰器时,本质上是进行了如下过程:

@staticmethod

def xxx():

   pass

等同于:

def xxx():

   pass

xxx = staticmethod(xxx)

可见,装饰器语法等同于使用装饰器函数处理原函数,并赋值回原函数的过程。

对于装饰器内部定义的新函数,其作为返回值返回后,应保持与原函数一致的函数参数声明,这样才能保证参数正确传递。而装饰器内部定义的函数由于具有通用性,是不能像普通函数一样定义有限多个形参的,故这个问题的解决方案为:使用不定长形参声明,并在函数调用时使用参数解包。

下例定义了一个常见的计时用装饰器:

import time

import numpy as np

def Timer(func):

   def newFunc(*args, **kwargs):

       sTime = time.time()

       returnTuple = func(*args, **kwargs)

       eTime = time.time()

       print('Time: %.6f' % (eTime - sTime))

       return returnTuple

   return newFunc

@Timer

def Test(timesNum):

   for i in range(timesNum):

       np.random.rand(100, 100).dot(np.random.rand(100, 100))

Test(10)

上述代码定义了一个名为Timer的装饰器函数,这个函数接受一个函数作为参数,并在内部定义了一个声明为def newFunc(*args, **kwargs)的通用函数,在这个函数内部,以解包参数*args, **kwargs调用了装饰器传入的函数,并在调用前后保存了调用时间。最终输出消耗时间,并返回函数的返回值。上述这个函数定义,最终作为装饰器函数的返回值返回。在装饰器外部,这个返回的函数将覆盖原函数。所以,在调用被Timer装饰的Test函数时,函数不仅会执行Test函数的内容,还会执行Timer装饰器中所定义的附加内容,即虽然调用的是Test函数,但实际执行的是以Test函数作为参数的newFunc函数。

3 有参装饰器

有参装饰器是“返回装饰器函数的函数”,本人目前并不了解这种语法的具体应用场景,故这里只对有参装饰器做简单的语法上的讨论。

仍然以上文中的计时函数为例,有参装饰器可以使用参数修改装饰器的行为,如定义时间缩放:

import time

import numpy as np

def Timer(mulNum):

   def innerTimer(func):

       def newFunc(*args, **kwargs):

           sTime = time.time()

           returnTuple = func(*args, **kwargs)

           eTime = time.time()

           print('Time: %.6f' % ((eTime - sTime) * mulNum))

           return returnTuple

       return newFunc

   return innerTimer

@Timer(1000)

def Test(timesNum):

   for i in range(timesNum):

       np.random.rand(100, 100).dot(np.random.rand(100, 100))

Test(10)

上述代码中,Timer是一个有参装饰器,其参数用于定义时间缩放值。Timer应返回一个装饰器,故整个装饰器的定义被放在了Timer内部。而innerTimer为装饰器函数,其最终被Timer返回。innerTimer应接受一个函数作为参数,并返回一个新的函数,故在innerTimer内部定义了一个新的函数作为返回值,这个函数的定义中包含了对传入的函数参数func的调用,同时包含了计时语句,并最终利用时间,以及有参装饰器的参数mulNum共同计算输出值。innerTimer装饰器函数返回在其内部定义的函数newFunc,而Timer装饰器函数返回innerTimer装饰器函数,并最终将此装饰器交给被装饰的原函数。

综上,有参装饰器本质上是一个“返回(无参)装饰器函数的任意函数”,而无参装饰器函数是一个“接受函数作为唯一参数,并返回一个新的函数的函数”。有参装饰器只要求返回值是一个无参装饰器即可,而无参装饰器函数将修饰被其装饰的原函数,从而将原函数覆盖为一个装饰后的新函数。从而使得后续代码中所有对原函数的调用,实际调用的都是修饰后的新函数。

你可能感兴趣的:(Python进阶话题杂谈(十四)装饰器)