Ricerca…


introduzione

Le funzioni del decoratore sono schemi di progettazione del software. Modificano dinamicamente la funzionalità di una funzione, metodo o classe senza dover utilizzare direttamente le sottoclassi o modificare il codice sorgente della funzione decorata. Se utilizzati correttamente, i decoratori possono diventare strumenti potenti nel processo di sviluppo. Questo argomento riguarda l'implementazione e le applicazioni delle funzioni di decoratore in Python.

Sintassi

  • def decorator_function (f): pass # definisce un decoratore chiamato decorator_function

  • @decorator_function
    def decorated_function (): passa # la funzione è ora spostata (decorata da) decorator_function

  • decorated_function = decorator_function (decorated_function) # equivale a usare lo zucchero sintattico @decorator_function

Parametri

Parametro Dettagli
f La funzione da decorare (avvolta)

Funzione Decoratore

I decoratori aumentano il comportamento di altre funzioni o metodi. Qualsiasi funzione che prende una funzione come parametro e restituisce una funzione aumentata può essere utilizzata come decoratore .

# This simplest decorator does nothing to the function being decorated. Such
# minimal decorators can occasionally be used as a kind of code markers.
def super_secret_function(f):
    return f

@super_secret_function
def my_function():
    print("This is my secret function.")

La @ -notation è zucchero sintattico che è equivalente al seguente:

my_function = super_secret_function(my_function)

È importante tenerlo presente per capire come funzionano i decoratori. Questa sintassi "unsugared" rende chiaro il motivo per cui la funzione decoratore prende una funzione come argomento e perché dovrebbe restituire un'altra funzione. Dimostra anche cosa succederebbe se non si restituisse una funzione:

def disabled(f):
    """
    This function returns nothing, and hence removes the decorated function
    from the local scope.
    """
    pass

@disabled
def my_function():
    print("This function can no longer be called...")

my_function()
# TypeError: 'NoneType' object is not callable

Pertanto, di solito definiamo una nuova funzione all'interno del decoratore e la restituiamo. Questa nuova funzione dovrebbe prima fare qualcosa che deve fare, quindi chiamare la funzione originale e infine elaborare il valore di ritorno. Considera questa semplice funzione di decoratore che stampa gli argomenti ricevuti dalla funzione originale, quindi li chiama.

#This is the decorator
def print_args(func):
    def inner_func(*args, **kwargs):
        print(args)
        print(kwargs)
        return func(*args, **kwargs) #Call the original function with its arguments.
    return inner_func

@print_args
def multiply(num_a, num_b):
    return num_a * num_b
  
print(multiply(3, 5))
#Output:
# (3,5) - This is actually the 'args' that the function receives.
# {} - This is the 'kwargs', empty because we didn't specify keyword arguments.
# 15 - The result of the function.

Classe decoratore

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

Fare un decoratore assomiglia alla funzione decorata

I decoratori normalmente spogliano i metadati della funzione perché non sono gli stessi. Ciò può causare problemi quando si utilizza la meta-programmazione per accedere in modo dinamico ai metadati delle funzioni. I metadati includono anche le docstring della funzione e il suo nome. functools.wraps rende la funzione decorata simile alla funzione originale copiando diversi attributi alla funzione wrapper.

from functools import wraps

I due metodi di avvolgere un decoratore stanno ottenendo la stessa cosa nascondendo che la funzione originale è stata decorata. Non vi è alcun motivo per preferire la versione della funzione alla versione della classe a meno che non si stia già utilizzando uno sull'altro.

Come una funzione

def decorator(func):
    # Copies the docstring, name, annotations and module to the decorator
    @wraps(func)
    def wrapped_func(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapped_func

@decorator
def test():
    pass

test.__name__

'test'

Come classe

class Decorator(object):
    def __init__(self, func):
        # Copies name, module, annotations and docstring to the instance.
        self._wrapped = wraps(func)(self)
        
    def __call__(self, *args, **kwargs):
        return self._wrapped(*args, **kwargs)

@Decorator
def test():
    """Docstring of test."""
    pass

test.__doc__

"Docstring of test".

Decoratore con argomenti (fabbrica di decorazioni)

Un decoratore accetta un solo argomento: la funzione da decorare. Non c'è modo di passare altri argomenti.

Ma spesso si desiderano ulteriori argomenti. Il trucco è quindi di creare una funzione che accetta argomenti arbitrari e restituisce un decoratore.

Funzioni del decoratore

def decoratorfactory(message):
    def decorator(func):
        def wrapped_func(*args, **kwargs):
            print('The decorator wants to tell you: {}'.format(message))
            return func(*args, **kwargs)
        return wrapped_func
    return decorator

@decoratorfactory('Hello World')
def test():
    pass

test()

L'arredatore vuole dirti: Hello World

Nota importante:

Con queste fabbriche di decoratori devi chiamare il decoratore con una coppia di parentesi:

@decoratorfactory # Without parentheses
def test():
    pass

test()

TypeError: decorator () mancante 1 argomento posizionale richiesto: 'func'

Lezioni di decoratore

def decoratorfactory(*decorator_args, **decorator_kwargs):
    
    class Decorator(object):
        def __init__(self, func):
            self.func = func

        def __call__(self, *args, **kwargs):
            print('Inside the decorator with arguments {}'.format(decorator_args))
            return self.func(*args, **kwargs)
        
    return Decorator

@decoratorfactory(10)
def test():
    pass

test()

All'interno del decoratore con argomenti (10,)

Crea una classe singleton con un decoratore

Un singleton è un pattern che limita l'istanza di una classe a un'istanza / oggetto. Usando un decoratore, possiamo definire una classe come un singleton forzando la classe a restituire un'istanza esistente della classe o creare una nuova istanza (se non esiste).

def singleton(cls):    
    instance = [None]
    def wrapper(*args, **kwargs):
        if instance[0] is None:
            instance[0] = cls(*args, **kwargs)
        return instance[0]

    return wrapper

Questo decoratore può essere aggiunto a qualsiasi dichiarazione di classe e si assicurerà che al massimo venga creata una sola istanza della classe. Qualsiasi chiamata successiva restituirà l'istanza di classe già esistente.

@singleton
class SomeSingletonClass:
    x = 2
    def __init__(self):
        print("Created!")

instance = SomeSingletonClass()  # prints: Created!
instance = SomeSingletonClass()  # doesn't print anything
print(instance.x)                # 2

instance.x = 3
print(SomeSingletonClass().x)    # 3

Quindi non importa se ti riferisci all'istanza della classe tramite la tua variabile locale o se crei un'altra "istanza", ottieni sempre lo stesso oggetto.

Utilizzare un decoratore per cronometrare una funzione

import time
def timer(func):
    def inner(*args, **kwargs):
        t1 = time.time()
        f = func(*args, **kwargs)
        t2 = time.time()
        print 'Runtime took {0} seconds'.format(t2-t1)
        return f
    return inner

@timer
def example_function():
    #do stuff


example_function()


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow