Python: gestire le eccezioni con <strong>assert</strong> e validazione dei precondizioni

by theArchitect
SHARE
Python: gestire le eccezioni con <strong>assert</strong> e validazione dei precondizioni
© Guida-HTML5.it

Introduzione

Quando si parla di gestione delle eccezioni in Python, spesso si pensa subito a try, except e raise. Esiste però un sotto-argomento molto utile, pratico e spesso sottovalutato: l’uso di assert per verificare le precondizioni di una funzione o di un blocco di codice.

assert non sostituisce la gestione delle eccezioni tradizionale, ma la affianca in modo intelligente. Serve a controllare che certe condizioni siano vere durante lo sviluppo, così da intercettare subito errori di logica, input incoerenti o stati interni non validi. È uno strumento particolarmente utile in codice tecnico, librerie, pipeline di dati e funzioni che assumono requisiti precisi prima di lavorare correttamente.

In questo tutorial vedremo come usare assert per validare precondizioni, quando è appropriato usarlo, quali errori evitare e come integrarlo con una gestione robusta delle eccezioni.

Codice completo

def calcola_sconto(prezzo, percentuale, cliente_attivo):
    # Precondizioni: il prezzo deve essere positivo
    assert prezzo > 0, "Il prezzo deve essere maggiore di zero"

    # La percentuale di sconto deve essere compresa tra 0 e 100
    assert 0 <= percentuale <= 100, "La percentuale deve essere tra 0 e 100"

    # Il cliente deve essere attivo per ottenere lo sconto
    assert cliente_attivo is True, "Lo sconto è disponibile solo per clienti attivi"

    sconto = prezzo * (percentuale / 100)
    prezzo_finale = prezzo - sconto
    return round(prezzo_finale, 2)


def normalizza_lista_valori(valori):
    # La funzione si aspetta una lista non vuota
    assert isinstance(valori, list), "L´input deve essere una lista"
    assert len(valori) > 0, "La lista non può essere vuota"

    # Tutti i valori devono essere numeri
    for indice, valore in enumerate(valori):
        assert isinstance(valore, (int, float)), f"Elemento non numerico all´indice {indice}"

    massimo = max(valori)
    minimo = min(valori)

    # Evitiamo divisione per zero: se tutti i valori sono uguali,
    # restituiamo una lista di zeri.
    if massimo == minimo:
        return [0 for _ in valori]

    return [(v - minimo) / (massimo - minimo) for v in valori]


def esegui_demo():
    try:
        print("Prezzo finale:", calcola_sconto(100, 20, True))
        print("Valori normalizzati:", normalizza_lista_valori([10, 15, 25]))
        print("Caso con input errato:", calcola_sconto(-50, 10, True))
    except AssertionError as errore:
        print("Errore di precondizione intercettato:", errore)


if __name__ == "__main__":
    esegui_demo()

Spiegazione

assert è una istruzione che verifica una condizione booleana. Se la condizione è falsa, Python genera un’eccezione AssertionError con un messaggio opzionale. In pratica, è un modo rapido per dire: “questa funzione funziona solo se l’input rispetta certe regole”.

Come funziona

  • Se la condizione è vera, il programma prosegue normalmente.
  • Se la condizione è falsa, viene sollevato AssertionError.
  • Il messaggio associato all’assert aiuta a capire subito cosa non va.

Nel primo esempio, calcola_sconto() richiede tre precondizioni:

  • il prezzo deve essere positivo;
  • la percentuale deve stare tra 0 e 100;
  • il cliente deve essere attivo.

Se una di queste condizioni fallisce, la funzione si interrompe immediatamente. Questo è utile perché evita di eseguire calcoli su dati invalidi e rende il bug più facile da individuare.

Nel secondo esempio, normalizza_lista_valori() usa assert per garantire che:

  • l’input sia una lista;
  • la lista non sia vuota;
  • ogni elemento sia numerico.

Una volta superati i controlli, la funzione può lavorare con maggiore sicurezza. Inoltre, gestisce il caso speciale in cui tutti i valori siano uguali, evitando una divisione per zero.

Perché usare assert e non sempre try/except?

try/except serve a gestire errori che possono accadere durante l’esecuzione. assert, invece, serve a verificare assunzioni interne del codice. In altre parole:

  • try/except gestisce l’imprevisto;
  • assert controlla che il codice stia ricevendo o mantenendo dati validi.

Questo lo rende particolarmente utile per individuare bug di sviluppo. Se una funzione riceve un valore impossibile, probabilmente il problema non è da “recuperare” ma da correggere alla fonte.

Attenzione: assert non è validazione pubblica dell’input

Un punto importante: assert non dovrebbe essere usato come unico meccanismo di validazione per dati provenienti dall’esterno, come input utente, richieste HTTP o file non affidabili. Il motivo è che assert può essere disabilitato eseguendo Python in modalità ottimizzata con l’opzione -O.

Quindi, per dati esterni, è meglio usare controlli espliciti e sollevare eccezioni appropriate, ad esempio ValueError o TypeError. assert è più adatto a precondizioni interne, invarianti e controlli di coerenza durante lo sviluppo.

Best practice

  • Usa assert per le precondizioni interne, non per validare input esterni non affidabili.
  • Scrivi messaggi chiari nell’assert: devono spiegare subito cosa non è valido.
  • Non abusarne: se una condizione è parte della logica normale di business, meglio una eccezione esplicita.
  • Controlla tipi e valori separatamente quando serve maggiore leggibilità.
  • Non usare assert per effetti collaterali: l’espressione deve essere solo un controllo.
  • Ricorda che può essere disabilitato: non affidarti ad esso per proteggere operazioni critiche in produzione.

Un buon criterio pratico è questo: se il problema indica un errore di programmazione, assert è spesso appropriato; se invece indica un dato non valido ma plausibile nel mondo reale, meglio una eccezione esplicita.

Esempio di scelta corretta

Se una funzione interna riceve una lista già filtrata dal tuo codice, puoi usare assert per verificare che non sia vuota. Se invece la lista arriva da un utente, è meglio fare un controllo esplicito e sollevare un errore gestibile.

def media(valori):
    if not valori:
        raise ValueError("La lista non può essere vuota")

    return sum(valori) / len(valori)

In questo caso, la lista vuota è un problema prevedibile e recuperabile, quindi ValueError è più adatto di assert.

Riepilogo

  • assert serve a verificare precondizioni e invarianti.
  • Se una condizione fallisce, Python solleva AssertionError.
  • È utile per scoprire bug di logica e stati interni incoerenti.
  • Non va usato come sostituto della validazione robusta di input esterni.
  • Per errori di dati o di business logic, spesso è meglio usare raise con eccezioni standard o personalizzate.

In breve: assert è uno strumento semplice ma molto potente per scrivere codice più sicuro e più facile da debuggare, soprattutto quando vuoi dichiarare chiaramente le condizioni che devono essere vere prima di procedere.

Approfondisci con risorse ufficiali

  • Documentazione Python su assert: cerca la sezione dedicata all’istruzione assert nel tutorial ufficiale di Python.
  • Documentazione Python sulle eccezioni: utile per confrontare AssertionError, ValueError e TypeError.
  • PEP 8: linee guida sullo stile del codice Python, inclusi messaggi e leggibilità.
  • Python docs - Built-in Exceptions: elenco completo delle eccezioni integrate e dei casi d’uso.

SHARE