Corso Python / Decoratori e context manager: ritorni, eccezioni e pulizia

Un decoratore avvolge il comportamento di chiamata di una funzione, mentre un context manager organizza ingresso e pulizia intorno a un blocco with. Entrambi aiutano a separare operazioni comuni, ma devi controllare che valori restituiti ed eccezioni conservino il significato desiderato.

Distinguere il wrapper dalla gestione della durata

@announce non è un commento: dopo la definizione della funzione equivale ad assegnare total = announce(total). Il nome total viene associato al wrapper restituito dal decoratore. La chiamata passa quindi attraverso quel wrapper prima di raggiungere la funzione originale.

with gestisce invece una durata delimitata: prepara il valore da usare nel blocco e applica la logica di uscita. Con contextlib.contextmanager puoi descrivere questo passaggio con un generatore che produce un solo valore. Il finally dell’esempio svuota una lista anche se il blocco termina con un errore.

functools.wraps aiuta a conservare nome, documentazione e collegamento alla funzione avvolta. Non convalida gli argomenti e non garantisce da solo che il wrapper rispetti tutti i contratti della funzione. Consulta la documentazione di contextlib e quella di functools.wraps per i rispettivi comportamenti.

Eseguire l’esempio completo

In una cartella di esercitazione salva questo file UTF-8 come wrappers.py. Usa soltanto la libreria standard, senza installazioni pip:

from functools import wraps
from contextlib import contextmanager

def announce(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("start")
        return func(*args, **kwargs)
    return wrapper

@contextmanager
def temporary_items():
    items = []
    try:
        yield items
    finally:
        items.clear()
        print("cleared")

@announce
def total(amounts):
    return sum(amounts)

with temporary_items() as items:
    items.extend([3500, 8000])
    print(total(items))
print(total.__name__)
print(items)

Dal terminale nella stessa cartella esegui python wrappers.py con il tuo ambiente selezionato, oppure python3 se è il nome dell’interprete disponibile. Il risultato atteso è:

start
11500
cleared
total
[]

La lista riceve 3500 e 8000. Il wrapper stampa start, poi restituisce la somma originale, che viene stampata come 11500. All’uscita dal with, il context manager svuota la stessa lista e stampa cleared. Il nome resta total grazie a wraps.

La variabile items esiste anche dopo il blocco: with non crea un nuovo ambito per i nomi. Punta alla lista ora vuota, quindi l’ultima stampa mostra []. La pulizia non ha cancellato la variabile; ha modificato l’oggetto che quella variabile riferisce.

Seguire ritorni ed eccezioni attraverso i due livelli

*args e **kwargs inoltrano gli argomenti posizionali e nominati. Il return del wrapper è essenziale: senza di esso la funzione originale verrebbe comunque chiamata, ma il chiamante riceverebbe None. La stampa di avvio non è una prova che il valore restituito sia stato conservato.

Nel context manager il codice prima di yield prepara la lista; il valore prodotto viene assegnato dopo as; la conclusione del blocco fa riprendere il generatore. Se il blocco solleva un’eccezione, questa viene riportata nel generatore nel punto di yield. Il finally esegue la pulizia, poi l’errore continua verso il chiamante.

Se intercetti un errore intorno a yield e non lo rilanci, puoi sopprimerlo. Fallo soltanto quando rappresenta una politica di recupero intenzionale: altrimenti chi chiama non saprà che l’operazione è fallita. Per stampare la lista dopo un errore nell’esercizio, il chiamante deve intercettare quell’errore con un try/except esterno al with; senza gestione, le istruzioni successive non vengono eseguite.

Il decoratore announce non intercetta eccezioni della funzione originale e non contiene una pulizia di fine chiamata. Se vuoi compiere un’operazione anche dopo una chiamata fallita, devi progettarla esplicitamente. Le regole di try, except e finally rimangono valide anche dentro un wrapper.

Provare la pulizia e scegliere l’astrazione giusta

  1. Dopo la somma, all’interno del with, inserisci temporaneamente raise ValueError("test"). Esegui il file e controlla che compaia cleared, pur terminando con un errore. Intercetta l’errore fuori dal blocco se vuoi anche ispezionare la lista vuota.
  2. Ripristina il programma. Nel wrapper cambia soltanto return func(*args, **kwargs) in func(*args, **kwargs). La stampa del risultato deve diventare None, mentre la pulizia continua a funzionare.
  3. Ripristina il return e riesegui: le cinque righe devono coincidere con l’uscita originale.

Ogni chiamata a temporary_items() crea un nuovo contesto per una nuova lista. Non riutilizzare la stessa istanza del context manager generatore come se fosse riavviabile dopo la sua uscita; chiama nuovamente la funzione quando serve un altro blocco.

Un decoratore di tentativi automatici può ripetere una richiesta che modifica dati e creare transazioni duplicate. La politica di retry è distinta dalla pulizia delle risorse. Per un’operazione piccola usata una sola volta, un blocco esplicito può essere più chiaro di una nuova astrazione: scegli in base alle regole che devi rendere visibili e verificabili.