Python: creare un package con <strong>__init__.py</strong> e gestire l’API pubblica

by theArchitect
SHARE
Python: creare un package con <strong>__init__.py</strong> e gestire l’API pubblica
© Guida-HTML5.it

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.

SHARE