Introduzione
Una funzione Python è un blocco di codice con un compito preciso. Può ricevere dati in ingresso, elaborarli e produrre un risultato. Imparare a definire e chiamare una funzione è uno dei primi passi per scrivere programmi ordinati, ma nelle applicazioni reali è altrettanto importante rendere le funzioni comprensibili e prevedibili.
In questo tutorial vedremo come progettare funzioni utilizzando docstring e annotazioni di tipo. Questi strumenti non modificano il funzionamento fondamentale della funzione, ma documentano il suo comportamento e aiutano editor, strumenti di analisi e altri programmatori a utilizzarla correttamente.
Il sotto-argomento è particolarmente utile quando una funzione viene richiamata in più punti del programma. Una funzione ben descritta chiarisce:
- quali dati si aspetta;
- quale tipo di risultato restituisce;
- che cosa accade durante l’elaborazione;
- quali condizioni o errori devono essere considerati.
Codice completo
from datetime import date
def calcola_eta(data_nascita: date, oggi: date | None = None) -> int:
"""Calcola l´età di una persona in anni compiuti.
Args:
data_nascita: Data di nascita della persona.
oggi: Data di riferimento. Se assente, viene usata la data corrente.
Returns:
L´età della persona espressa in anni interi.
Raises:
ValueError: Se la data di nascita è nel futuro.
"""
if oggi is None:
oggi = date.today()
if data_nascita > oggi:
raise ValueError("La data di nascita non può essere futura")
eta = oggi.year - data_nascita.year
# Se il compleanno non è ancora passato, sottraiamo un anno.
compleanno_non_trascorso = (
(oggi.month, oggi.day) < (data_nascita.month, data_nascita.day)
)
if compleanno_non_trascorso:
eta -= 1
return eta
nascita = date(2000, 12, 20)
data_riferimento = date(2026, 10, 8)
eta_persona = calcola_eta(nascita, data_riferimento)
print(f"L´età calcolata è: {eta_persona} anni") Spiegazione
Definire una funzione
Una funzione si definisce con la parola chiave def, seguita dal nome della funzione e dalle parentesi tonde. Dopo le parentesi si inseriscono i due punti e un blocco indentato:
def saluta(nome: str) -> str:
"""Restituisce un messaggio di saluto."""
return f"Ciao, {nome}!" In questo esempio, nome: str è un’annotazione che indica il tipo atteso del parametro. L’annotazione -> str indica invece che la funzione dovrebbe restituire una stringa. Python non obbliga automaticamente a rispettare questi tipi durante l’esecuzione, ma strumenti come mypy, Pyright e molti editor possono controllarli.
Chiamare una funzione
Definire una funzione non significa eseguirla. Per avviarla è necessario chiamarla usando il suo nome e fornendo gli argomenti richiesti:
messaggio = saluta("Giulia")
print(messaggio) Durante la chiamata, il valore "Giulia" viene assegnato al parametro nome. La funzione costruisce la stringa e la restituisce tramite return. Il risultato può essere stampato, salvato in una variabile oppure passato a un’altra funzione.
Comprendere la docstring
La stringa posizionata subito dopo l’intestazione della funzione è chiamata docstring. Descrive lo scopo della funzione e può essere visualizzata direttamente da Python:
help(calcola_eta) Una docstring utile non deve ripetere semplicemente il nome della funzione. Deve spiegare il significato dei parametri, il risultato e gli errori possibili. Nel codice completo, la sezione Raises documenta l’eccezione generata quando la data di nascita è futura.
Perché usare una data di riferimento
La funzione calcola_eta può essere chiamata senza specificare oggi. In quel caso utilizza la data corrente. Tuttavia, durante i test è preferibile fornire una data esplicita, perché il risultato diventa riproducibile:
eta = calcola_eta(
date(1995, 5, 10),
date(2025, 5, 9)
)
print(eta) # 29, perché il compleanno non è ancora passato Separare il calcolo dalla lettura automatica della data corrente rende la funzione più facile da verificare e riutilizzare.
Gestire gli errori
Quando un dato non è valido, è meglio segnalare il problema con un’eccezione significativa invece di restituire un valore ambiguo come -1 o None. Chi chiama la funzione può gestire l’errore con try ed except:
try:
eta = calcola_eta(date(2030, 1, 1), date(2026, 10, 8))
except ValueError as errore:
print(f"Errore nei dati: {errore}") Best practice
- Scegli nomi descrittivi: preferisci
calcola_etaa nomi generici comefunzione1. - Segui il principio di una sola responsabilità: una funzione dovrebbe svolgere un compito ben delimitato.
- Aggiungi annotazioni di tipo: chiariscono il contratto della funzione e migliorano l’autocompletamento.
- Scrivi docstring per le funzioni pubbliche: documenta parametri, risultato ed eccezioni rilevanti.
- Evita effetti collaterali non dichiarati: una funzione di calcolo non dovrebbe modificare file o variabili globali senza motivo.
- Rendi i risultati prevedibili: passa come argomento i dati che possono cambiare, come una data corrente o una configurazione.
- Valida gli input importanti: un errore esplicito è più semplice da diagnosticare di un risultato apparentemente corretto.
- Limita la lunghezza delle funzioni: se una funzione diventa difficile da leggere, suddividila in funzioni più piccole.
È inoltre buona pratica testare sia i casi normali sia quelli limite. Per calcola_eta si possono verificare una data di compleanno già trascorsa, una non ancora trascorsa e una data futura.
Riepilogo
Definire una funzione significa creare un’unità di codice riutilizzabile; chiamarla significa eseguirla fornendo gli argomenti necessari. Le annotazioni di tipo rendono evidente quali dati sono attesi e quale risultato viene prodotto, mentre le docstring spiegano il comportamento della funzione in modo consultabile.
Una funzione ben progettata ha un nome chiaro, un compito specifico, input validati e un risultato prevedibile. L’uso di eccezioni appropriate e di dati espliciti, come la data di riferimento nell’esempio, migliora inoltre la qualità dei test e la manutenzione del programma.
