Corso Python / Annotazioni di tipo, mypy e validazione degli ingressi
Le annotazioni di tipo descrivono gli ingressi e i risultati attesi per chi legge il codice e per gli strumenti di analisi statica. Python non le applica automaticamente come controlli sui valori: una verifica scritta nella funzione e un controllo con mypy svolgono compiti diversi.
Leggere un contratto senza presumere una conversione
amount: int comunica che l’argomento atteso è un intero; non equivale a chiamare int sul valore ricevuto. -> str descrive il risultato atteso come stringa, ma non trasforma automaticamente ciò che la funzione restituisce.
Le annotazioni aiutano editor e verificatori a trovare relazioni di tipo incompatibili nel codice. L’esecuzione normale continua invece a dipendere dalle istruzioni effettive e dai controlli che hai scritto. La documentazione ufficiale di typing precisa questo confine.
list[int] descrive una lista di interi; str | None descrive una stringa oppure l’assenza di un valore. Gli esempi di questa lezione sono destinati a Python 3.12 o successivo. Se un programma legge e interpreta dinamicamente le annotazioni, deve considerare le regole di valutazione della versione usata invece di presumere che siano identiche in tutte le versioni.
Confrontare annotazioni e controlli espliciti
Salva il programma completo come hints.py, in UTF-8, in una cartella di esercizio. Esegui python hints.py dal terminale con l’interprete del progetto selezionato. Fuori dall’ambiente virtuale su macOS o Ubuntu usa python3 se è il comando scelto. Per eseguire questo programma non servono pacchetti esterni.
def format_amount(amount: int) -> str:
if type(amount) is not int or amount < 0:
raise ValueError("invalid amount")
return f"{amount:,} KRW"
def find_name(names: dict[str, str], key: str) -> str | None:
return names.get(key)
print(format_amount(8000))
print(find_name({"e1": "lunch"}, "missing"))
try:
format_amount("8000")
except ValueError:
print("rejected")
Il risultato previsto è:
8,000 KRW
None
rejected
La funzione format_amount rifiuta la stringa "8000" grazie all’istruzione if e al ValueError, non per effetto dell’annotazione. Il controllo richiede esattamente un int non negativo: anche True e valori negativi vengono rifiutati, mentre zero è ammesso. La formattazione con virgola e l’etichetta KRW riguardano solo la stringa mostrata.
Find_name usa get e restituisce None se la chiave manca. Il tipo di ritorno dichiara proprio questa possibilità. La gestione delle eccezioni nel blocco finale rende visibile il rifiuto previsto senza arrestare l’intera prova.
Eseguire un verificatore statico sullo stesso file
Per confrontare l’esecuzione con l’analisi statica, puoi installare mypy nell’ambiente virtuale del progetto e controllare il file:
python -m pip install mypy
python -m mypy hints.py
Mypy deve segnalare come incompatibile l’argomento stringa nella chiamata intenzionale format_amount("8000"). Questo è il risultato atteso della prova, anche se eseguendo lo stesso file ottieni rejected e una conclusione normale. Il verificatore analizza il codice senza eseguire quella chiamata; il programma applica invece il controllo soltanto quando viene eseguito.
Per confrontare un caso corretto, crea una copia chiamata hints_fixed.py e sostituisci soltanto quella chiamata con format_amount(8000), mantenendo valido il blocco try/except. Controlla la copia con python -m mypy hints_fixed.py: non deve contenere quell’errore di argomento. In alternativa elimina l’intero blocco try/except della prova negativa, senza lasciare un try vuoto. La formulazione precisa dei messaggi dipende dalla versione del verificatore.
| Attività | Che cosa controlla |
|---|---|
| Analisi statica | Relazioni di tipo ricavabili dal codice e dalle annotazioni |
| Validazione durante l’esecuzione | Valori effettivi, intervalli, campi necessari e altre regole esplicite |
La guida ufficiale introduttiva a mypy descrive installazione e analisi. Non cambiare indiscriminatamente i tipi in Any solo per eliminare i messaggi: ridurresti le informazioni utili al controllo. Allo stesso tempo un controllo statico riuscito non garantisce che una risorsa esterna esista o che un importo abbia un valore consentito.
Gestire None e i confini degli ingressi
Prima di chiamare un metodo di stringa sul risultato di find_name, verifica se è None. Dopo un controllo come if value is not None:, il ramo corrispondente può usare il valore come stringa. Senza quel controllo rischi AttributeError durante l’esecuzione e un verificatore può segnalare l’accesso su un tipo che include None.
Il tipo int da solo non esprime «non negativo». Una chiamata con -1 ha un argomento di tipo intero e può superare l’analisi dei tipi, ma il controllo della funzione la rifiuta comunque. In modo analogo, decidere se convertire una stringa numerica arrivata da JSON o rifiutarla resta una politica dell’ingresso, da applicare esplicitamente.
- Esegui hints.py e confronta le tre righe previste.
- Analizza lo stesso file con mypy e riconosci l’errore intenzionale della chiamata con stringa.
- Analizza la copia con la chiamata intera e verifica che quell’errore sia scomparso, poi esegui la copia: rejected non compare perché non viene più sollevata la stessa eccezione.
- Ripristina l’originale per la prova negativa e verifica anche i casi None, zero e importo negativo separatamente.
Controlla che annotazioni e risultati reali corrispondano in ogni ramo della funzione. L’analisi statica rende più visibili le aspettative del codice; i test e i controlli durante l’esecuzione verificano come vengono trattati i dati effettivi.




