Vai al contenuto principale

Come usare i decorator in Python (con esempi basati su funzioni e classi)

Impara i decorator in Python con esempi pratici. Capisci closure, decorator basati su funzioni e classi e come scrivere codice riutilizzabile ed elegante.
Aggiornato 3 giu 2026  · 11 min leggi

I decorator sono una funzionalità potente ed elegante di Python che ti permette di modificare o estendere il comportamento di funzioni e metodi senza cambiarne il codice.

Un decorator è un pattern di progettazione in Python che consente di aggiungere nuove funzionalità a un oggetto esistente senza modificarne la struttura. I decorator si applicano tipicamente alle funzioni e svolgono un ruolo fondamentale nel migliorare o modificare il comportamento delle funzioni. Tradizionalmente, i decorator si collocano prima della definizione della funzione che vuoi decorare. In questo tutorial, ti mostreremo come usare efficacemente i decorator nelle funzioni Python.

TL;DR

  • I decorator ti permettono di modificare o estendere il comportamento delle funzioni senza cambiare il codice originale
  • Usa la sintassi @decorator_name per applicare i decorator in modo pulito
  • Usa sempre functools.wraps per preservare i metadati della funzione quando scrivi decorator
  • I decorator basati su classi permettono un comportamento con stato tra chiamate successive
  • Casi d’uso comuni includono caching (@lru_cache), logging, autenticazione e validazione degli input

Funzioni come oggetti di prima classe

Le funzioni in Python sono cittadini di prima classe. Questo significa che supportano operazioni come essere passate come argomento, restituite da una funzione, modificate e assegnate a una variabile. Questa proprietà è cruciale perché consente di trattare le funzioni come qualsiasi altro oggetto in Python, garantendo maggiore flessibilità nella programmazione.

Per eseguire facilmente tu stesso tutto il codice di esempio in questo tutorial, puoi creare gratuitamente un workbook DataLab che ha Python preinstallato e contiene tutti gli esempi di codice. Per fare più pratica con i decorator, dai un’occhiata a questo esercizio pratico su DataCamp.

Assegnare funzioni a variabili

Per iniziare, creiamo una funzione che aggiunge uno a un numero ogni volta che viene chiamata. Poi assegneremo la funzione a una variabile e useremo questa variabile per chiamare la funzione.

def plus_one(number):
    return number + 1

add_one = plus_one
add_one(5)
6

Definire funzioni dentro altre funzioni 

Definire funzioni dentro altre funzioni è una caratteristica potente di Python — ed è essenziale per costruire i decorator. Vediamo un’altra idea chiave: passare funzioni come argomenti. Ci porterà un passo più vicino a scrivere decorator.

def plus_one(number):
    def add_one(number):
        return number + 1


    result = add_one(number)
    return result
plus_one(4)
5

Passare funzioni come argomenti ad altre funzioni

Le funzioni possono anche essere passate come parametri ad altre funzioni. Vediamolo qui sotto.

def plus_one(number):
    return number + 1

def function_call(function):
    number_to_add = 5
    return function(number_to_add)

function_call(plus_one)
6

Funzioni che restituiscono altre funzioni

Una funzione può anche generare un’altra funzione. Lo mostriamo qui sotto con un esempio.

def hello_function():
    def say_hi():
        return "Hi"
    return say_hi
hello = hello_function()
hello()
'Hi'

Funzioni interne e closure

Python consente a una funzione annidata di accedere all’ambito esterno della funzione che la racchiude. Questo è un concetto fondamentale nei decorator, noto come closure.

Una closure in Python è una funzione che ricorda l’ambiente in cui è stata creata, anche dopo che quell’ambiente non è più attivo. Ciò significa che una funzione annidata può “chiudere su” variabili dal suo ambito esterno e continuare a usarle.

Le closure sono essenziali per capire i decorator perché questi si basano sulla capacità di una funzione wrapper annidata di accedere e modificare lo stato della funzione decorator che la racchiude.

Esempio di closure:

def outer_function(message):
    def inner_function():
        print(f"Message from closure: {message}")
    return inner_function

closure_function = outer_function("Hello, closures!")
closure_function()
# Output: Message from closure: Hello, closures!

In questo esempio:

  • inner_function è una closure perché accede a message, una variabile dal suo ambito esterno (outer_function).
  • Anche se outer_function ha terminato l’esecuzione, inner_function mantiene l’accesso a message.

Quando crei un decorator, la funzione wrapper (all’interno del decorator) è una closure. Mantiene l’accesso alla funzione decorata e a eventuale stato aggiuntivo o argomenti definiti nella funzione decorator. Per esempio:

def simple_decorator(func):
    def wrapper():
        print("Before the function call")
        func()
        print("After the function call")
    return wrapper

@simple_decorator
def greet():
    print("Hello!")

greet()
# Output:
# Before the function call
# Hello!
# After the function call

Qui, wrapper è una closure che ricorda la funzione greet e aggiunge un comportamento prima e dopo la sua esecuzione.

Creare il tuo primo decorator

Ora che hai capito le closure — la capacità di una funzione di ricordare le variabili del suo ambito esterno — siamo pronti a creare il nostro primo vero decorator. Le closure sono l’ingrediente segreto che permette ai decorator di funzionare dietro le quinte.

Creiamo un semplice decorator che converte una frase in maiuscolo. Lo facciamo definendo un wrapper dentro una funzione racchiudente. Come puoi vedere, è molto simile alla funzione dentro un’altra funzione che abbiamo creato prima.

def uppercase_decorator(function):
    def wrapper():
        func = function()
        make_uppercase = func.upper()
        return make_uppercase

    return wrapper

Dato che il nostro decorator accetta una funzione come argomento, definiremo una nuova funzione e la passeremo al decorator. Abbiamo visto in precedenza che possiamo assegnare una funzione a una variabile. Useremo questo trucco per chiamare la nostra funzione decorator.

def say_hi():
    return 'hello there'

decorate = uppercase_decorator(say_hi)
decorate()
'HELLO THERE'

Usare la sintassi @

Tuttavia, Python offre un modo molto più semplice per applicare i decorator. Basta usare il simbolo @ prima della funzione che vogliamo decorare. Vediamolo in pratica qui sotto.

@uppercase_decorator
def say_hi():
    return 'hello there'

say_hi()
'HELLO THERE'

Impilare più decorator

Una volta che ti senti a tuo agio con la sintassi @ per un singolo decorator, puoi fare un passo oltre e impilarne più di uno sulla stessa funzione. Tieni solo a mente: l’ordine conta! 

Qui sotto definiremo un altro decorator che divide la frase in una lista. Poi applicheremo i decorator uppercase_decorator e split_string a una singola funzione.

import functools
def split_string(function):
    @functools.wraps(function)
    def wrapper():
        func = function()
        splitted_string = func.split()
        return splitted_string

    return wrapper 
@split_string
@uppercase_decorator
def say_hi():
    return 'hello there'
say_hi()
['HELLO', 'THERE']

Dal risultato sopra, notiamo che l’applicazione dei decorator avviene dal basso verso l’alto. Se avessimo invertito l’ordine, avremmo ottenuto un errore perché le liste non hanno l’attributo upper. La frase è stata prima convertita in maiuscolo e poi divisa in una lista.

Nota: Quando impili dei decorator, è prassi comune usare functools.wraps per garantire che i metadati della funzione originale vengano preservati durante tutto il processo. Questo aiuta a mantenere chiarezza e coerenza nel debug e nella comprensione delle proprietà della funzione decorata.

Accettare argomenti nei decorator

Finora abbiamo visto decorator che fanno solo wrapping di una funzione. Ma se vuoi configurare il decorator stesso — ad esempio passando dei parametri? È qui che entrano in gioco le factory di decorator.

def decorator_with_arguments(function):
    def wrapper_accepting_arguments(arg1, arg2):
        print("My arguments are: {0}, {1}".format(arg1,arg2))
        function(arg1, arg2)
    return wrapper_accepting_arguments


@decorator_with_arguments
def cities(city_one, city_two):
    print("Cities I love are {0} and {1}".format(city_one, city_two))

cities("Nairobi", "Accra")

I miei argomenti sono: Nairobi, Accra Le città che amo sono Nairobi e Accra

Nota: È essenziale assicurarsi che il numero di argomenti nel decorator (
arg1, arg2 in questo esempio) corrisponda al numero di argomenti nella funzione wrappata (cities in questo esempio). Questo allineamento è fondamentale per evitare errori e garantire il corretto funzionamento quando si usano decorator con argomenti.

Decorator generici con *args e **kwargs

Per definire un decorator generico applicabile a qualsiasi funzione usiamo *args e **kwargs. *args e **kwargs raccolgono tutti gli argomenti posizionali e con parola chiave e li memorizzano nelle variabili *args e **kwargs. *args e **kwargs ci permettono di passare quanti argomenti vogliamo durante le chiamate di funzione.

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    def a_wrapper_accepting_arbitrary_arguments(*args,**kwargs):
        print('The positional arguments are', args)
        print('The keyword arguments are', kwargs)
        function_to_decorate(*args)
    return a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print("No arguments here.")

function_with_no_argument()
Gli argomenti posizionali sono ()
Gli argomenti con parola chiave sono {}
Nessun argomento qui.

Vediamo come useremmo il decorator con argomenti posizionali.

@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print(a, b, c)

function_with_arguments(1,2,3)
Gli argomenti posizionali sono (1, 2, 3)
Gli argomenti con parola chiave sono {}
1 2 3

Ecco come puoi passare argomenti con parola chiave a una funzione decorata:

@a_decorator_passing_arbitrary_arguments
def function_with_keyword_arguments():
    print("This has shown keyword arguments")

function_with_keyword_arguments(first_name="Derrick", last_name="Mwiti")
Gli argomenti posizionali sono ()
Gli argomenti con parola chiave sono {'first_name': 'Derrick', 'last_name': 'Mwiti'}
This has shown keyword arguments

Nota: L’uso di **kwargs nel decorator gli consente di gestire gli argomenti con parola chiave. Questo rende il decorator generico versatile e capace di gestire una varietà di tipi di argomenti durante le chiamate di funzione.

Passare argomenti ai decorator

Ora vediamo come passare argomenti al decorator stesso. Per farlo, definiamo un “decorator maker” che accetta argomenti e poi definiamo al suo interno un decorator. Quindi definiamo una funzione wrapper dentro il decorator, come fatto in precedenza.

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2, decorator_arg3):
    def decorator(func):
        def wrapper(function_arg1, function_arg2, function_arg3) :
            "This is the wrapper function"
            print("The wrapper can access all the variables\n"
                  "\t- from the decorator maker: {0} {1} {2}\n"
                  "\t- from the function call: {3} {4} {5}\n"
                  "and pass them to the decorated function"
                  .format(decorator_arg1, decorator_arg2,decorator_arg3,
                          function_arg1, function_arg2,function_arg3))
            return func(function_arg1, function_arg2,function_arg3)

        return wrapper

    return decorator

pandas = "Pandas"
@decorator_maker_with_arguments(pandas, "Numpy","Scikit-learn")
def decorated_function_with_arguments(function_arg1, function_arg2,function_arg3):
    print("This is the decorated function and it only knows about its arguments: {0}"
           " {1}" " {2}".format(function_arg1, function_arg2,function_arg3))

decorated_function_with_arguments(pandas, "Science", "Tools")
The wrapper can access all the variables
    - from the decorator maker: Pandas Numpy Scikit-learn
    - from the function call: Pandas Science Tools
and pass them to the decorated function
This is the decorated function, and it only knows about its arguments: Pandas Science Tools

Debuggare i decorator

Come abbiamo visto, i decorator wrappano le funzioni. Il nome della funzione originale, la sua docstring e la lista dei parametri sono tutti nascosti dalla closure del wrapper: per esempio, quando proviamo ad accedere ai metadati di decorated_function_with_arguments, vedremo i metadati della closure wrapper. Questo rappresenta una sfida in fase di debug.

decorated_function_with_arguments.__name__
'wrapper'
decorated_function_with_arguments.__doc__
'This is the wrapper function'

Per risolvere questo problema Python fornisce il decorator functools.wraps. Questo decorator copia i metadati persi dalla funzione non decorata nella closure decorata. Vediamo come farlo.

import functools

def uppercase_decorator(func):
    @functools.wraps(func)
    def wrapper():
        return func().upper()
    return wrapper
@uppercase_decorator
def say_hi():
    "This will say hi"
    return 'hello there'

say_hi()
'HELLO THERE'

Ora, quando controlliamo i metadati di say_hi, vediamo che riflettono correttamente la funzione originale — non il wrapper.

say_hi.__name__
'say_hi'
say_hi.__doc__
'This will say hi'

È consigliabile ed è una buona pratica usare sempre functools.wraps quando definisci decorator. Ti eviterà molti mal di testa in fase di debug.

Decorator basati su classi

Anche se i decorator basati su funzioni sono comuni, Python ti permette anche di creare decorator basati su classi, che offrono maggiore flessibilità e manutenibilità, soprattutto per casi d’uso complessi. Se sei alle prime armi con la programmazione orientata agli oggetti in Python, ripassare le basi ti aiuterà a comprendere meglio questa sezione. Un decorator basato su classe è una classe con un metodo __call__ che le permette di comportarsi come una funzione.

class UppercaseDecorator:
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        result = self.function(*args, **kwargs)
        return result.upper()

@UppercaseDecorator
def greet():
    return "hello there"

print(greet())
# Output: HELLO THERE

Come funziona:

  1. Il metodo __init__ inizializza il decorator con la funzione da decorare.
  2. Il metodo __call__ viene invocato quando la funzione decorata è chiamata, permettendo al decorator di modificarne il comportamento.

Vantaggi dei decorator basati su classi:

  • Decorator con stato: i decorator basati su classi possono mantenere stato usando variabili d’istanza, a differenza di quelli basati su funzioni che richiedono closure o variabili globali.
  • Leggibilità: per decorator complessi, incapsulare la logica in una classe può rendere il codice più organizzato e facile da capire.

Esempio di decorator con stato:

class CallCounter:
    def __init__(self, function):
        self.function = function
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Function {self.function.__name__} has been called {self.count} times.")
        return self.function(*args, **kwargs)

@CallCounter
def say_hello():
    print("Hello!")

say_hello()
say_hello()
# Output:
# Function say_hello has been called 1 times.
# Hello!
# Function say_hello has been called 2 times.
# Hello!

Caso d’uso reale dei decorator: caching

Il decorator lru_cache è uno strumento integrato in Python che mette in cache i risultati di chiamate di funzione costose. Questo migliora le prestazioni evitando calcoli ridondanti per input ripetuti.

Esempio:

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(50))  # Subsequent calls with the same argument are much faster

Altri usi comuni dei decorator:

  • Logging: traccia le chiamate di funzione, gli argomenti e i valori di ritorno per il debug o l’auditing.

  • Autenticazione: applica il controllo degli accessi in applicazioni web come Flask o Django.

  • Misurazione dei tempi di esecuzione: misura e ottimizza il tempo di esecuzione delle funzioni per task critici in termini di performance.

  • Meccanismo di retry: ritenta automaticamente chiamate di funzione fallite, utile nelle operazioni di rete.

  • Validazione degli input: valida gli argomenti della funzione prima dell’esecuzione.

Riepilogo sui decorator in Python

I decorator modificano dinamicamente la funzionalità di una funzione, metodo o classe senza dover usare direttamente sottoclassi o cambiare il codice sorgente della funzione decorata. Usare i decorator in Python assicura anche che il tuo codice sia DRY (Don’t Repeat Yourself). I decorator hanno diversi casi d’uso, come:

  • Autorizzazione in framework Python come Flask e Django
  • Logging
  • Misurazione del tempo di esecuzione
  • Sincronizzazione

Per saperne di più sui decorator in Python dai un’occhiata alla Decorator Library di Python.

FAQs

Ci sono considerazioni sulle prestazioni quando si usano i decorator?

Sì, i decorator possono aggiungere overhead perché introducono chiamate di funzione aggiuntive. Quando le prestazioni sono critiche, è importante considerare questo overhead, soprattutto se la funzione decorata viene chiamata spesso in contesti sensibili alle performance.

I decorator possono essere usati con i metodi di classe? Se sì, come?

Sì, i decorator possono essere applicati ai metodi di classe proprio come alle normali funzioni. Il decorator riceverà il metodo come argomento e restituirà un nuovo metodo o una versione modificata del metodo. Questo è comunemente usato per logging, controllo accessi o per imporre precondizioni.

Come si possono usare i decorator per il logging?

I decorator possono essere usati per registrare le chiamate di funzione, i loro argomenti e i valori di ritorno wrappando l’esecuzione della funzione con codice che registra questi dettagli in un sistema di logging. Questo aiuta nel tracing e nel debug.

Qual è il significato del simbolo @ nei decorator?

Il @ è zucchero sintattico in Python che semplifica l’applicazione di un decorator a una funzione. Ti permette di applicare un decorator direttamente sopra la definizione della funzione, rendendo il codice più pulito e leggibile.

Un decorator può modificare il valore di ritorno di una funzione? Come?

Sì, un decorator può modificare il valore di ritorno di una funzione alterando l’istruzione di return all’interno della funzione wrapper. Per esempio, può trasformare il tipo di output, formattarlo o aggiungere elaborazioni aggiuntive prima di restituire il risultato finale.

Come gestisce Python l’ambito delle variabili quando una funzione annidata accede a una variabile della funzione che la contiene?

Python usa la regola di ambito LEGB (Local, Enclosing, Global, Built-in). Nel caso di funzioni annidate, la funzione annidata può accedere alle variabili dell’ambito della funzione che la racchiude, il che consente le closure: la funzione interna mantiene l’accesso alle variabili della funzione esterna anche dopo che quest’ultima ha terminato l’esecuzione.

Qual è la differenza tra una closure e un decorator?

Una closure è una funzione che ricorda le variabili dal suo ambito esterno. Un decorator è una funzione che usa le closure per arricchire o wrappare altre funzioni.

Quando dovrei usare functools.wraps?

Usa sempre functools.wraps quando scrivi decorator. Preserva i metadati originali della funzione (come nome e docstring), il che è utile per debug e documentazione.

I decorator possono accettare argomenti?

Sì! Puoi passare argomenti ai decorator wrappandoli in un’altra funzione (una factory di decorator). Questo ti permette di personalizzare il comportamento del decorator.

Qual è il vantaggio dei decorator basati su classi?

I decorator basati su classi ti permettono di mantenere stato tra le chiamate di funzione e organizzare logiche più complesse in modo orientato agli oggetti.

I decorator sono solo per le funzioni?

No. Puoi usare i decorator anche su classi e metodi. In framework come Flask e Django, i decorator sono comunemente usati su route, view e model.

Argomenti

Approfondisci Python

Corso

Introduzione alle funzioni in Python

3 h
466K
Impara a scrivere le tue funzioni in Python ed esplora concetti chiave come lo scoping e la gestione degli errori.
Vedi dettagliRight Arrow
Inizia il corso
Mostra altroRight Arrow
Correlato

blog

Tokenizzazione nel NLP: come funziona, sfide e casi d'uso

Guida al preprocessing NLP nel machine learning. Copriamo spaCy, i transformer di Hugging Face e come funziona la tokenizzazione in casi d'uso reali.
Abid Ali Awan's photo

Abid Ali Awan

10 min

blog

Che cos'è Snowflake? Guida per principianti alla piattaforma dati cloud

Esplora le basi di Snowflake, la piattaforma dati cloud. Scopri la sua architettura, le sue funzionalità e come integrarla nelle tue pipeline di dati.
Tim Lu's photo

Tim Lu

12 min

blog

I 15 migliori server MCP remoti che ogni AI builder dovrebbe conoscere nel 2026

Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Abid Ali Awan's photo

Abid Ali Awan

15 min

Mostra altroMostra altro