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


有的时候,一个装饰器不够用。例如,假设我们编写两个将要在开发过程中使用的函数装饰器一个用来在函数调用之前测试参数类型,另一个用来在函数调用之后测试返回值类型。我们可以独立地使用任何一个,但是如果想要在一个单独的函数上同时使用这二者,我们真正需要的是嵌套这两者的方法,这样其中一个装饰器的结果就是被另一个装饰器所装饰的函数。哪个装饰器被嵌套是无关紧要的,只要这两个步骤在随后调用时运行。为了以这种方式支持多个嵌套步骤的扩展,装饰器语法允许我们向一个被装饰函数或方法添加多个层的包装器逻辑。当使用这一功能的时候,每个装饰器必须出现在自己的这一行中。这种形式的装饰器语法为:

@A
@B
@C
def f():
    pass

运行起来与下面的代码相同:

def f():
    pass

f = A(B(C(f)))

这里,将最初的函数传递进三个不同的装饰器,并且最终的可调用对象分配回给了最初的名称。每个装饰器处理前一个的结果,这可能是最初的函数或是一个插人的包装器。

如果所有的装饰器都插人包装器,直接的效果就是,当调用最初的函数名时,将会调用包装对象逻辑的三个不同的层,从而以三种不同的方式扩展最初的函数。当最初的函数名称随后被调用时,首先应用上述所列的最后一个装饰器,这也是嵌套最深的装饰器。

同函数一样,多个类装饰器导致了多个嵌套的函数调用,并且可能导致围绕实例创建调用的包装器逻辑的多个层与多个步骤。例如下面的代码:

@A
@B
@C
class Class_A:
    pass

X = Class_A()

等同于如下的代码:

class Class_A:
    pass

Class_A = A(B(C(Class_A )))
X = Class_A ()

每个装饰器都可自由地返回最初的类或所插人的包装器对象。有了包装器,当请求最初Class_A类的一个实例的时候,这一调用会重定向至ABC装饰器所提供的包装层对象,这些对象可以有多种不同角色一一例如它们能够追踪和验证属性访问,这两个步骤都会在接下来请求的时候运行。

例如,如下的装饰器什么也不做,只是返回被装饰的函数:

def d1(F):
    return F

def d2(F):
    return F

def d3(F):
    return F

@d1
@d2
@d3
def func():
    print('machinelearning.blog.csdn.net')

func()

输出:

machinelearning.blog.csdn.net

同样的语法在类上也有效,就像这里什么也不做的装饰器一样。然而,当装饰器插人包装器函数对象后,调用的时候它们可能扩展最初的函数。下面的代码从内向外地运行各装饰器层,并在各层中拼接其结果:

def d1(F):
    return lambda: 'CSDN' + F()

def d2(F):
    return lambda: 'Blog' + F()

def d3(F):
    return lambda: ':' + F()

@d1
@d2
@d3
def func():
    return 'machinelearning.blog.csdn.net'

func()

输出:

CSDNBlog:machinelearning.blog.csdn.net

我们在这里使用了lambda函数来实现包装器层(每个都在一个外层作用域里保留了被包装的函数)。实践中,包装器可以采取函数、可调用类等形式。设计良好的装饰器嵌套允许我们以多种多样的方式组合扩展步骤。

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