分类目录:《系统学习Python》总目录


函数装饰器已经从Python2.4开始可用。正如我们在前文所见到的,它们大体上是一种语法糖:在def语句结束时通过另一个函数来运行这个函数,把最初的函数名重新绑定到返回的结果。

装饰器自身是一个返回可调用对象的可调用对象。也就是说,它返回了一个对象,当通过其最初名称调用被装饰函数的时候,将会调用这个对象一一一它可以是拦截之后调用的一个包装器对象,也可以是以某种方式扩展的最初函数。实际上,装饰器可以是任意类型的可调用对象,并且返回任意类型的可调用对象:函数和类的任何组合都可以使用,尽管一些组合更适合于特定的情景。

例如,要在一个函数创建之后接人装饰协议以管理函数,我们需要编写如下形式的装饰器:

def decorator(F):
    pass      # 对F的操作
    return F

@decorator
def F(arg):
    pass

F(x)

由于最初的被装饰函数拿回了它的名称,这么做相当于直接向函数的定义添加创建后步骤。这样的结构可以用于把一个函数注册到一个API,赋值函数属性等。

更典型的用法,是插入逻辑以拦截之后对函数的调用,我们可以编写一个装饰器返回和最初函数不同的一个对象一一一一个用于之后调用的代理:

def decorator(F):
    pass      # 保存或使用F,返回另一个可调用对象
    return G

@decorator
def F(arg):
    pass

这个装饰器在装饰的时候调用,并且当之后调用最初的函数名的时候,它所返回的调用对象将被调用。装饰器自身接受被装饰的函数;返回的调用对象会接受随后传递给被装饰函数名称的任何参数。当恰当地编写装饰器时,这和类级别方法的工作方式相同:隐含的实例对象只在返回的可调用对象的第一个参数中出现。

更概括地说,有一种常用的编程模式可以捕捉这一思想一一装饰器返回了一个包装器,包装器把最初的函数保持在一个外层作用域中:

def decorator(F):
    def wrapper(*args):
        pass    # 使用F和*args
        return wrapper

@decorator
def F(arg):
    pass

当随后调用名称func的时候,它实际调用了decorator所返回的wrapper函数;包装器函数可能会运行最初的func,因为它在外层作用域中仍然可以使用。当以这种方式编程的时候,每个被装饰的函数都会产生一个新的作用域来保持状态。

为了应用类实现同样的装饰器,我们可以重载调用操作,并且使用实例属性而不是外层作用域:

def decorator(F):
    def __init__(self, func):
        self.func = func
    def __call__(self, *args):
        pass    # 使用self.func和*args

@decorator
def F(arg):
    pass

现在,之后再调用func的时候,它确实会调用装饰器所创建实例的__call__运算符重载方法;然后,__call__方法可能运行最初的func,因为它在一个实例属性中仍然可用。当按照这种方式编写代码的时候,每个被装饰的函数都会产生一个新的实例来保持状态。

参考文献:
[1] Mark Lutz. Python学习手册[M]. 机械工业出版社, 2018.