Introduzione
Quando si parla di funzioni in Python, spesso ci si concentra solo su come chiamarle e su cosa restituiscono. In realtà, progettare bene argomenti, parametri e valori di ritorno significa scrivere codice più leggibile, meno soggetto a errori e più facile da riutilizzare.
Un aspetto molto utile, ma spesso trascurato, è la validazione esplicita degli input. In pratica, una funzione non dovrebbe limitarsi a “funzionare” quando riceve dati corretti: dovrebbe anche reagire in modo chiaro quando riceve valori sbagliati. Questo è fondamentale in script, API, strumenti da riga di comando e applicazioni reali, dove gli input possono arrivare da file, utenti o servizi esterni.
In questo tutorial vedremo come progettare una funzione che riceve parametri ben definiti, controlla la validità degli argomenti e restituisce un risultato utile e coerente. L’obiettivo non è solo imparare la sintassi, ma sviluppare un approccio pratico e professionale alla scrittura di funzioni Python.
Codice completo
from dataclasses import dataclass
@dataclass
class InvoiceResult:
subtotal: float
tax_amount: float
total: float
def calcola_totale_fattura(
prezzo_unitario: float,
quantita: int,
aliquota_iva: float = 0.22,
sconto_percentuale: float = 0.0
) -> InvoiceResult:
"""
Calcola il totale di una fattura con validazione degli input.
Parametri:
prezzo_unitario: prezzo di un singolo articolo, deve essere > 0
quantita: numero di articoli, deve essere > 0
aliquota_iva: aliquota IVA espressa come valore decimale, ad esempio 0.22
sconto_percentuale: sconto espresso come valore decimale, ad esempio 0.10
Restituisce:
Un oggetto InvoiceResult con subtotale, importo IVA e totale finale.
"""
# Validazione dei parametri
if prezzo_unitario <= 0:
raise ValueError("prezzo_unitario deve essere maggiore di 0")
if quantita <= 0:
raise ValueError("quantita deve essere maggiore di 0")
if not (0 <= aliquota_iva <= 1):
raise ValueError("aliquota_iva deve essere compresa tra 0 e 1")
if not (0 <= sconto_percentuale < 1):
raise ValueError("sconto_percentuale deve essere compreso tra 0 e 1")
# Calcoli intermedi
subtotal = prezzo_unitario * quantita
subtotal_scontato = subtotal * (1 - sconto_percentuale)
tax_amount = subtotal_scontato * aliquota_iva
total = subtotal_scontato + tax_amount
return InvoiceResult(
subtotal=round(subtotal_scontato, 2),
tax_amount=round(tax_amount, 2),
total=round(total, 2)
)
def descrivi_risultato(risultato: InvoiceResult) -> str:
"""
Trasforma il risultato in una stringa leggibile.
"""
return (
f"Subtotale: {risultato.subtotal:.2f} EURn"
f"IVA: {risultato.tax_amount:.2f} EURn"
f"Totale: {risultato.total:.2f} EUR"
)
if __name__ == "__main__":
fattura = calcola_totale_fattura(
prezzo_unitario=19.90,
quantita=3,
aliquota_iva=0.22,
sconto_percentuale=0.10
)
print(descrivi_risultato(fattura)) Spiegazione
La funzione calcola_totale_fattura è un buon esempio di progettazione chiara dei parametri. Riceve quattro input:
- prezzo_unitario: il prezzo di un singolo prodotto;
- quantita: il numero di prodotti acquistati;
- aliquota_iva: un valore opzionale con default impostato a 0.22;
- sconto_percentuale: un altro parametro opzionale, utile per applicare promozioni.
Qui si vede già un primo vantaggio dei parametri con valore predefinito: la funzione può essere usata in casi semplici senza dover specificare ogni volta tutti gli argomenti. Per esempio, se non c’è sconto, il chiamante può omettere sconto_percentuale.
La parte più importante, però, è la validazione. Prima di eseguire i calcoli, la funzione controlla che:
- il prezzo sia maggiore di zero;
- la quantità sia maggiore di zero;
- l’IVA sia compresa tra 0 e 1;
- lo sconto sia compreso tra 0 e 1.
Se uno di questi vincoli non è rispettato, la funzione solleva una ValueError. Questo è un comportamento molto utile: invece di produrre un risultato sbagliato o ambiguo, il programma si interrompe con un messaggio chiaro.
Il valore di ritorno non è un semplice numero, ma un oggetto InvoiceResult creato con dataclass. Questo approccio è più pulito di restituire una tupla, perché rende esplicito il significato di ogni campo. Invece di scrivere qualcosa come risultato[0] o risultato[1], possiamo usare attributi leggibili come risultato.subtotal, risultato.tax_amount e risultato.total.
La funzione descrivi_risultato mostra un altro pattern utile: separare il calcolo dalla presentazione. Una funzione calcola i dati, un’altra li trasforma in testo. Questo rende il codice più modulare e facile da testare.
Esempio di utilizzo:
- chiamata con argomenti nominati per migliorare la leggibilità;
- default per i parametri opzionali;
- ritorno strutturato e autoesplicativo.
Se il programma riceve un input errato, ad esempio quantita=0, la funzione non continua in silenzio: segnala subito l’errore. Questo è esattamente il tipo di comportamento che rende una funzione robusta in contesti reali.
Best practice
- Valida sempre gli input critici: non fidarti del fatto che i dati arrivino già corretti.
- Usa nomi di parametri chiari:
prezzo_unitarioè molto più comprensibile dip. - Preferisci valori di ritorno espliciti: una
dataclasso un dizionario ben strutturato è spesso meglio di una tupla anonima. - Separa calcolo e formattazione: una funzione deve avere una responsabilità principale.
- Usa eccezioni appropriate:
ValueErrorè adatta quando il tipo è corretto ma il contenuto non è valido. - Documenta il contratto della funzione: spiega cosa accetta e cosa restituisce.
- Rendi i default sensati: i parametri opzionali devono rappresentare casi comuni, non nascondere comportamenti ambigui.
Un errore frequente è restituire valori diversi in base ai casi, ad esempio un numero quando tutto va bene e None quando c’è un problema. Questo rende il codice chiamante più complicato. Meglio usare eccezioni per gli errori e un formato stabile per il risultato.
Un’altra buona pratica è evitare di fare troppe cose nella stessa funzione. Se una funzione deve validare, calcolare, salvare su file e stampare a video, sta diventando troppo grande. In quel caso conviene dividerla in più funzioni più piccole e specializzate.
Riepilogo
Argomenti, parametri e valori di ritorno non sono solo dettagli sintattici: sono il cuore del design di una funzione Python. In questo tutorial abbiamo visto un caso pratico in cui la funzione:
- riceve parametri obbligatori e opzionali;
- valida gli input prima di procedere;
- solleva eccezioni chiare quando i dati non sono validi;
- restituisce un oggetto strutturato e facile da usare.
Questo approccio migliora la qualità del codice, facilita il debug e rende le funzioni più affidabili in scenari reali. Se impari a progettare bene i parametri e i ritorni, scriverai API più pulite e programmi più robusti.
Approfondisci con risorse ufficiali
- Documentazione Python sulle funzioni:
https://docs.python.org/3/tutorial/controlflow.html#defining-functions - Documentazione sulle dataclass:
https://docs.python.org/3/library/dataclasses.html - Documentazione sulle eccezioni:
https://docs.python.org/3/tutorial/errors.html - PEP 8 - Style Guide for Python Code:
https://peps.python.org/pep-0008/
