裝飾器類

正如在介紹中提到的,裝飾器是一個可以應用於另一個函式來增強其行為的函式。語法糖相當於以下: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