9.9 将装饰器定义为类

    为了将装饰器定义成一个实例,你需要确保它实现了 和 get() 方法。例如,下面的代码定义了一个类,它在其他函数上放置一个简单的记录层:

    你可以将它当做一个普通的装饰器来使用,在类里面或外面都可以:


    1. def add(x, y):
      return x + y

    在交互环境中的使用示例:

    将装饰器定义成类通常是很简单的。但是这里还是有一些细节需要解释下,特别是当你想将它作用在实例方法上的时候。

    首先,使用 functools.wraps() 函数的作用跟之前还是一样,将被包装函数的元信息复制到可调用实例中去。

    1. >>> s = Spam()
    2. >>> s.bar(3)
    3. Traceback (most recent call last):
    4. TypeError: bar() missing 1 required positional argument: 'x'

    出错原因是当方法函数在一个类中被查找时,它们的 方法依据描述器协议被调用,在8.9小节已经讲述过描述器协议了。在这里,get() 的目的是创建一个绑定方法对象(最终会给这个方法传递self参数)。下面是一个例子来演示底层原理:

    get() 方法是为了确保绑定方法对象能被正确的创建。type.MethodType() 手动创建一个绑定方法来使用。只有当实例被使用的时候绑定方法才会被创建。如果这个方法是在类上面来访问,那么 中的instance参数会被设置成None并直接返回 Profiled 实例本身。这样的话我们就可以提取它的 ncalls 属性了。

    如果你想避免一些混乱,也可以考虑另外一个使用闭包和 nonlocal 变量实现的装饰器,这个在9.5小节有讲到。例如:

    1. import types
    2. from functools import wraps
    3.  
    4. def profiled(func):
    5. ncalls = 0
    6. @wraps(func)
    7. def wrapper(*args, **kwargs):
    8. ncalls += 1
    9. return func(*args, **kwargs)
    10. wrapper.ncalls = lambda: ncalls
    11. return wrapper
    12.  
    13. # Example
    14. @profiled
    15. def add(x, y):

    这个方式跟之前的效果几乎一样,除了对于 的访问现在是通过一个被绑定为属性的函数来实现,例如: