Python Language Classe decoratore


Esempio

Come accennato nell'introduzione, un decoratore è una funzione che può essere applicata a un'altra funzione per aumentarne il comportamento. Lo zucchero sintattico è equivalente al seguente: my_func = decorator(my_func) . Ma cosa succede se il decorator era invece una classe? La sintassi funzionerebbe ancora, tranne che ora my_func viene sostituito con un'istanza della classe decorator . Se questa classe implementa il metodo magico __call__() , allora sarebbe ancora possibile usare my_func come se fosse una funzione:

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.

Nota che una funzione decorata con un decoratore di classe non sarà più considerata una "funzione" dal punto di vista del controllo dei caratteri:

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

Metodi di decorazione

Per i metodi di decorazione è necessario definire un __get__ __get__ aggiuntivo:

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()

Dentro il decoratore.

Avvertimento!

I decoratori di classe producono solo un'istanza per una funzione specifica, quindi decorare un metodo con un decoratore di classe condividerà lo stesso decoratore tra tutte le istanze di quella classe:

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