Introduzione
Quando un progetto Python cresce, non basta più mettere tutto in singoli file sparsi. I moduli e i pacchetti servono proprio a dare struttura, separare le responsabilità e rendere il codice più facile da mantenere. Un aspetto molto pratico, ma spesso sottovalutato, è la gestione dell’API pubblica del pacchetto: cioè decidere quali classi, funzioni e costanti devono essere facilmente importabili dall’esterno e quali invece devono rimanere dettagli interni.
In questo tutorial vedremo come usare __init__.py per costruire un package pulito, comodo da usare e ben organizzato. L’obiettivo è evitare import scomodi come from progetto.strumenti.calcoli import somma_totale quando potremmo offrire un’interfaccia più semplice, ad esempio from progetto import somma_totale.
Questo approccio è molto utile in librerie, progetti aziendali e applicazioni modulari, perché migliora leggibilità, manutenibilità e stabilità del codice.
Codice completo
Immaginiamo di voler creare un piccolo package chiamato shopkit, pensato per gestire sconti, prezzi e formattazione di un carrello. Vogliamo che chi usa il package possa importare le funzionalità principali direttamente dal livello superiore.
# Struttura del progetto:
#
# shopkit/
# ├── __init__.py
# ├── pricing.py
# ├── discounts.py
# └── formatting.py
#
# demo.py
# shopkit/pricing.py
def calcola_totale(prezzi):
"""Somma una lista di prezzi e restituisce il totale."""
return sum(prezzi)
def calcola_totale_con_iva(prezzi, aliquota=0.22):
"""Calcola il totale con IVA."""
totale = calcola_totale(prezzi)
return totale * (1 + aliquota)
# shopkit/discounts.py
def applica_sconto(totale, percentuale):
"""Applica uno sconto percentuale al totale."""
if percentuale < 0 or percentuale > 100:
raise ValueError("La percentuale deve essere compresa tra 0 e 100")
return totale * (1 - percentuale / 100)
def sconto_benvenuto():
"""Sconto fisso di benvenuto."""
return 10
# shopkit/formatting.py
def formatta_euro(importo):
"""Formatta un importo in euro."""
return f"€ {importo:.2f}"
def formatta_riepilogo(prezzi):
"""Crea una stringa riassuntiva dei prezzi."""
return ", ".join(f"€ {p:.2f}" for p in prezzi)
# shopkit/__init__.py
from .pricing import calcola_totale, calcola_totale_con_iva
from .discounts import applica_sconto, sconto_benvenuto
from .formatting import formatta_euro, formatta_riepilogo
__all__ = [
"calcola_totale",
"calcola_totale_con_iva",
"applica_sconto",
"sconto_benvenuto",
"formatta_euro",
"formatta_riepilogo",
]
# demo.py
from shopkit import (
calcola_totale,
calcola_totale_con_iva,
applica_sconto,
formatta_euro,
formatta_riepilogo,
)
prezzi = [19.90, 5.50, 12.00]
totale = calcola_totale(prezzi)
totale_scontato = applica_sconto(totale, 15)
totale_finale = calcola_totale_con_iva([totale_scontato])
print("Prezzi:", formatta_riepilogo(prezzi))
print("Totale:", formatta_euro(totale))
print("Totale scontato:", formatta_euro(totale_scontato))
print("Totale finale con IVA:", formatta_euro(totale_finale)) Spiegazione
Vediamo cosa succede in questo esempio.
1. I moduli contengono responsabilità separate
Ogni file ha un compito preciso:
- pricing.py gestisce i calcoli economici;
- discounts.py si occupa degli sconti;
- formatting.py formatta i valori per la stampa o l’interfaccia utente.
Questa separazione rende il codice più facile da testare e modificare. Se cambi la logica degli sconti, non devi toccare il resto del package.
2. __init__.py definisce l’interfaccia del package
Il file __init__.py viene eseguito quando importi il package. Qui abbiamo scelto di importare alcune funzioni dai moduli interni per esporle direttamente a chi usa shopkit.
Così, invece di scrivere:
from shopkit.pricing import calcola_totale l’utente può scrivere semplicemente:
from shopkit import calcola_totale Questo è molto utile perché rende il package più elegante e più facile da usare. In pratica, __init__.py diventa una sorta di “vetrina” delle funzionalità principali.
3. __all__ controlla ciò che viene esportato
La lista __all__ indica quali simboli dovrebbero essere considerati pubblici. È particolarmente utile quando qualcuno usa:
from shopkit import * Anche se in generale questo tipo di import è sconsigliato, __all__ aiuta a limitare ciò che viene esposto. Inoltre comunica chiaramente quali funzioni fanno parte dell’API ufficiale.
4. L’import relativo mantiene il package coerente
Nel file __init__.py abbiamo usato import relativi:
from .pricing import calcola_totale Il punto iniziale indica che il modulo si trova nello stesso package. Questo evita ambiguità e rende il codice più robusto se il package viene spostato o rinominato.
5. Il file demo mostra l’uso reale
Nel file demo.py importiamo direttamente dal package principale. Questo è il risultato desiderato: un’interfaccia semplice per chi usa il codice, senza costringerlo a conoscere la struttura interna completa.
Best practice
- Usa __init__.py per esporre solo l’API pubblica: non importare tutto indiscriminatamente, ma solo ciò che vuoi supportare stabilmente.
- Separa logica interna e interfaccia esterna: i moduli interni possono cambiare, ma l’API pubblica dovrebbe restare il più stabile possibile.
- Evita import profondi nei file esterni: preferisci from package import funzione invece di importare direttamente moduli interni, quando il package offre già un punto d’accesso pulito.
- Documenta chiaramente cosa è pubblico: una buona documentazione riduce errori e semplifica l’uso del package da parte di altri sviluppatori.
- Usa __all__ con criterio: non è obbligatorio, ma aiuta a dichiarare in modo esplicito le parti dell’API pensate per l’uso esterno.
- Non mettere troppa logica in __init__.py: il file deve rimanere leggero. Se diventa troppo complesso, rischia di rallentare gli import e di complicare la manutenzione.
- Evita effetti collaterali all’import: importare un package non dovrebbe avviare processi, leggere file pesanti o fare connessioni di rete.
Riepilogo
In questo tutorial abbiamo visto come costruire un package Python con __init__.py per offrire un’API pubblica chiara e semplice. Questa tecnica è molto utile quando vuoi che il tuo progetto sia facile da usare dall’esterno, senza obbligare chi lo importa a conoscere tutti i dettagli interni.
I punti chiave da ricordare sono:
- i moduli separano le responsabilità;
- __init__.py può aggregare e re-esporre le funzionalità principali;
- __all__ aiuta a definire cosa è pubblico;
- gli import relativi rendono il package più ordinato e portabile.
Se costruisci una libreria o un progetto medio-grande, questa organizzazione ti farà risparmiare tempo e ti aiuterà a mantenere il codice pulito nel lungo periodo.
Approfondisci con risorse ufficiali
- Python Documentation - Packages: documentazione ufficiale su moduli e pacchetti.
- Python Documentation - The import system: dettagli sul funzionamento del sistema di import.
- PEP 8: guida di stile ufficiale per scrivere codice Python leggibile e coerente.
- PEP 328: introduce e spiega gli import relativi.
- Python Tutorial - Modules: sezione del tutorial ufficiale dedicata ai moduli.
