Funzioni lambda come fabbriche di calcoli configurabili

by theArchitect
SHARE
Funzioni lambda come fabbriche di calcoli configurabili
© Guida-HTML5.it

Introduzione

In Python una funzione lambda è una funzione anonima, cioè una funzione priva di un nome dichiarato con def. La sua sintassi è compatta:

lambda argomenti: espressione

Le lambda vengono spesso associate a operazioni brevi e immediate. Tuttavia, un uso molto pratico consiste nel combinarle con le funzioni di ordine superiore, cioè funzioni capaci di ricevere altre funzioni come argomento o di restituirle come risultato.

In questo tutorial realizzeremo un piccolo sistema per calcolare prezzi configurabili. L’obiettivo è creare una funzione generatrice che produca diverse regole di calcolo, ad esempio:

  • applicare uno sconto percentuale;
  • aggiungere l’IVA;
  • arrotondare un importo;
  • combinare più regole in una sequenza.

Questo approccio è utile quando il comportamento di un programma deve cambiare in base alla configurazione, senza duplicare molte funzioni quasi identiche.

Codice completo

from functools import reduce


def crea_sconto(percentuale):
    """
    Restituisce una lambda che applica uno sconto percentuale.
    """
    fattore = 1 - percentuale / 100
    return lambda prezzo: prezzo * fattore


def crea_iva(percentuale):
    """
    Restituisce una lambda che aggiunge l´IVA a un prezzo.
    """
    fattore = 1 + percentuale / 100
    return lambda prezzo: prezzo * fattore


def crea_arrotondamento(cifre=2):
    """
    Restituisce una lambda che arrotonda un numero.
    """
    return lambda valore: round(valore, cifre)


def combina_regole(*regole):
    """
    Riceve più funzioni e restituisce una funzione composta.
    Le regole vengono applicate nell´ordine ricevuto.
    """
    return lambda valore: reduce(
        lambda risultato, regola: regola(risultato),
        regole,
        valore
    )


# Creiamo alcune regole configurabili
sconto_10 = crea_sconto(10)
iva_22 = crea_iva(22)
arrotonda_2 = crea_arrotondamento(2)

# Componiamo le regole in una pipeline di calcolo
calcola_prezzo_finale = combina_regole(
    sconto_10,
    iva_22,
    arrotonda_2
)

prezzi = [19.90, 45.50, 120.00]

for prezzo in prezzi:
    risultato = calcola_prezzo_finale(prezzo)
    print(f"Prezzo iniziale: {prezzo:.2f} euro")
    print(f"Prezzo finale:   {risultato:.2f} euro")
    print("-" * 30)

# Possiamo creare una configurazione completamente diversa
solo_sconto = combina_regole(
    crea_sconto(25),
    arrotonda_2
)

print(f"Prezzo con sconto del 25%: {solo_sconto(80):.2f} euro")

Spiegazione

Creare una funzione con una lambda

La funzione crea_sconto() riceve una percentuale e restituisce una nuova funzione:

def crea_sconto(percentuale):
    fattore = 1 - percentuale / 100
    return lambda prezzo: prezzo * fattore

Se chiamiamo crea_sconto(10), otteniamo una funzione che applica sempre uno sconto del 10%. La variabile fattore viene conservata dalla lambda anche dopo la conclusione di crea_sconto(). Questo comportamento è chiamato closure.

sconto_10 = crea_sconto(10)

print(sconto_10(50))
# Output: 45.0

La lambda non conosce soltanto il parametro prezzo: ricorda anche il valore di fattore creato nel contesto esterno.

Funzioni come dati

In Python le funzioni sono oggetti di prima classe. Possono essere assegnate a variabili, inserite in liste e passate ad altre funzioni.

regole = [
    crea_sconto(15),
    crea_iva(22),
    crea_arrotondamento(2)
]

Ogni elemento della lista è una funzione. Questo permette di rappresentare un comportamento come una configurazione modificabile, invece di scrivere molti blocchi if.

Comporre più lambda con reduce()

La funzione combina_regole() riceve un numero variabile di funzioni tramite *regole. La funzione reduce() applica ogni regola al risultato precedente.

lambda risultato, regola: regola(risultato)

Il flusso di calcolo può essere immaginato così:

  • si parte dal prezzo originale;
  • si applica lo sconto;
  • si applica l’IVA al nuovo valore;
  • si arrotonda il risultato finale.

L’ordine è importante. Applicare prima l’IVA e poi lo sconto può produrre un risultato diverso rispetto all’ordine inverso. Una pipeline di funzioni rende questo ordine esplicito e facilmente modificabile.

Perché usare una lambda in questo scenario?

Le lambda sono adatte perché ogni regola è breve, ha una sola espressione e non richiede una logica complessa. Inoltre, la funzione generatrice può creare regole personalizzate durante l’esecuzione del programma:

sconto_5 = crea_sconto(5)
sconto_30 = crea_sconto(30)

print(sconto_5(100))
print(sconto_30(100))

Lo stesso meccanismo può essere applicato a commissioni, conversioni di unità, tariffe di spedizione, normalizzazione di valori e configurazioni di report.

Best practice

  • Usa lambda solo per espressioni semplici. Se servono più istruzioni, variabili temporanee o controlli complessi, preferisci def.
  • Scegli nomi descrittivi per le funzioni restituite. Una variabile come calcola_prezzo_finale è più leggibile di un nome generico come f.
  • Documenta le funzioni generatrici. Chi usa crea_sconto() deve capire cosa produce e quali unità di misura utilizza.
  • Controlla i valori in ingresso. Una percentuale negativa o superiore a 100 potrebbe essere valida in alcuni casi, ma spesso indica un errore.
  • Non abusare di reduce(). Se la composizione diventa difficile da leggere, un ciclo for può essere più chiaro.
  • Considera la precisione monetaria. Per applicazioni finanziarie reali, float può introdurre piccoli errori di arrotondamento. Valuta il modulo decimal.
  • Evita lambda troppo lunghe. Una riga compatta non è automaticamente una riga leggibile.

Per esempio, una versione più esplicita di combina_regole() potrebbe essere preferibile in un progetto aziendale:

def combina_regole_chiara(*regole):
    def applica_regole(valore):
        risultato = valore

        for regola in regole:
            risultato = regola(risultato)

        return risultato

    return applica_regole

Questa variante produce lo stesso risultato, ma rende immediatamente visibile il ciclo di applicazione.

Riepilogo

Le funzioni lambda non servono soltanto a scrivere funzioni brevi in una singola riga. Usate insieme alle closure e alle funzioni di ordine superiore permettono di creare comportamenti configurabili e componibili.

Nel nostro esempio abbiamo:

  • generato funzioni personalizzate con crea_sconto() e crea_iva();
  • conservato configurazioni all’interno di closure;
  • trattato le funzioni come dati;
  • composto più regole in una pipeline;
  • separato la definizione delle regole dalla loro esecuzione.

Il principio fondamentale è semplice: una lambda è efficace quando esprime un’operazione breve e ben comprensibile. Se la logica cresce, è meglio passare a una funzione nominata con def.

Approfondisci con risorse ufficiali

SHARE