Python Language
decoratori
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_functiondecorated_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()