Python: argomenti, parametri e valori di ritorno nelle funzioni con validazione esplicita

by theArchitect
SHARE
Python: argomenti, parametri e valori di ritorno nelle funzioni con validazione esplicita
© Guida-HTML5.it

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 di p.
  • Preferisci valori di ritorno espliciti: una dataclass o 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/

SHARE