Funzioni Python: progettare API leggibili con docstring e annotazioni di tipo

by theArchitect
SHARE
Funzioni Python: progettare API leggibili con docstring e annotazioni di tipo
© Guida-HTML5.it

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_eta a nomi generici come funzione1.
  • 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.

Approfondisci con risorse ufficiali

SHARE