装饰器类

正如在介绍中提到的,装饰器是一个可以应用于另一个函数来增强其行为的函数。语法糖相当于以下:my_func = decorator(my_func)。但是如果 decorator 是一个类呢?语法仍然有效,除了现在 my_funcdecorator 类的实例替换。如果这个类实现了 __call__() 魔术方法,那么仍然可以使用 my_func,就好像它是一个函数:

class Decorator(object):
    """Simple decorator class."""

    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('Before the function call.')
        res = self.func(*args, **kwargs)
        print('After the function call.')
        return res

@Decorator
def testfunc():
    print('Inside the function.')

testfunc()
# Before the function call.
# Inside the function.
# After the function call.

请注意,使用类装饰器修饰的函数将不再被视为类型检查透视图中的函数

import types
isinstance(testfunc, types.FunctionType)
# False
type(testfunc)
# <class '__main__.Decorator'>

装饰方法

对于装饰方法,你需要定义一个额外的 __get__ 方法:

from types import MethodType

class Decorator(object):
    def __init__(self, func):
        self.func = func
        
    def __call__(self, *args, **kwargs):
        print('Inside the decorator.')
        return self.func(*args, **kwargs)
    
    def __get__(self, instance, cls):
        # Return a Method if it is called on an instance
        return self if instance is None else MethodType(self, instance)

class Test(object):
    @Decorator
    def __init__(self):
        pass
    
a = Test()

在装饰器里面。

警告!

类装饰器只为特定函数生成一个实例,因此使用类装饰器装饰方法将在该类的所有实例之间共享相同的装饰器:

from types import MethodType

class CountCallsDecorator(object):
    def __init__(self, func):
        self.func = func
        self.ncalls = 0    # Number of calls of this method
        
    def __call__(self, *args, **kwargs):
        self.ncalls += 1   # Increment the calls counter
        return self.func(*args, **kwargs)
    
    def __get__(self, instance, cls):
        return self if instance is None else MethodType(self, instance)

class Test(object):
    def __init__(self):
        pass
    
    @CountCallsDecorator
    def do_something(self):
        return 'something was done'
    
a = Test()
a.do_something()
a.do_something.ncalls   # 1
b = Test()
b.do_something()
b.do_something.ncalls   # 2