Python pratico

__name__, nome file e package: cosa cambia con le cartelle

12 min di lettura
Corridoio di un archivio con file di cassetti di legno numerati su entrambi i lati
Windows macOS Linux
Foto: Tima Miroshnichenko · Pexels · Licenza Pexels

Come cambia __name__ tra script, import e package: perché python file.py e python -m danno risultati diversi e quando un import relativo dà errore.

Nei due laboratori della guida Capire if __name__ == "__main__" con esempi i file stanno nella stessa cartella: se lanci il file, __name__ vale "__main__"; se lo importi, vale il suo nome. Poi sposti i file in una cartella con un __init__.py e il quadro cambia: il nome diventa app.util, lo stesso file lanciato in due modi dà due risultati, e un import relativo funziona in un caso e in un altro risponde con un ImportError. python app/util.py e python -m app.util danno lo stesso __name__ ma un __package__ diverso, ed è questa differenza a decidere se gli import relativi funzionano.

La risposta breve: dentro un package lancia i file con python -m nomepackage.modulo, dalla cartella che contiene il package, non con python percorso/del/file.py. Nella prova su Debian ho lanciato gli stessi file in più modi: con -m gli import relativi hanno funzionato, con il percorso del file no.

ℹ️ Nota / Prerequisito operativo: serve aver fatto almeno il Lab A e il Lab B della guida madre. Su Windows usa python al posto di python3 e la barra rovesciata nei percorsi (app\util.py); i comandi sono quelli che ho eseguito su Debian, e su Windows e macOS non li ho lanciati. I percorsi assoluti negli output di testo (per esempio /workspace/lab) sono quelli della mia macchina di prova, e nello screenshot sono /home/box/lab: sul tuo computer saranno diversi.

Il laboratorio: una cartella con un package

Crea una cartella lab con questi file. saluta.py è una variante di quello della guida madre: stampa anche __package__ e sys.path[0], cioè la cartella da cui Python cerca i moduli.

lab/
├── saluta.py
├── usa_saluta.py
└── app/
    ├── __init__.py
    ├── __main__.py
    ├── cli.py
    ├── cli_assoluto.py
    ├── percorso.py
    └── util.py

saluta.py:

import sys

print("saluta.py -> __name__ =", __name__)
print("saluta.py -> __package__ =", repr(__package__))
print("saluta.py -> sys.path[0] =", sys.path[0])

if __name__ == "__main__":
    print("saluta.py -> guardia superata")

usa_saluta.py:

import saluta

print("usa_saluta.py -> __name__ =", __name__)

app/__init__.py:

print("app/__init__.py -> __name__ =", __name__)

app/util.py:

print("app/util.py -> __name__ =", __name__)
print("app/util.py -> __package__ =", repr(__package__))


def saluta(nome):
    return f"Ciao, {nome}!"


if __name__ == "__main__":
    print("app/util.py -> guardia superata:", saluta("Alex"))

app/cli.py:

from . import util

print("app/cli.py -> __name__ =", __name__)

if __name__ == "__main__":
    print(util.saluta("Lettore"))

Il file piatto: i due valori che già conosci

Dalla cartella lab, su Windows:

python saluta.py

Su macOS e Linux:

python3 saluta.py
saluta.py -> __name__ = __main__
saluta.py -> __package__ = None
saluta.py -> sys.path[0] = /workspace/lab
saluta.py -> guardia superata

Lanciato direttamente, il file ha __name__ uguale a __main__. La documentazione di Python lo conferma: il modulo passato all’interprete come argomento è il «top-level code environment». Con python3 usa_saluta.py il file saluta.py è stato importato e ha stampato __name__ = saluta; in più, nella prova, __package__ valeva '' (una stringa vuota) e non None: un file importato direttamente dalla sua cartella non appartiene a nessun package.

Lo stesso file diventa app.util

Con util.py dentro app/ il nome cambia. Il comando, sempre dalla cartella lab:

python3 -c "import app.util"
app/__init__.py -> __name__ = app
app/util.py -> __name__ = app.util
app/util.py -> __package__ = 'app'

Il __name__ di un modulo importato è il suo percorso con i punti: app.util. È quello che la documentazione dice per i file dentro un package (l’esempio ufficiale è concurrent.futures.process). Nota l’ordine: Python esegue prima app/__init__.py e poi util.py. E la guardia if __name__ == "__main__" non è scattata, perché il file è stato importato.

Lanciare un file del package: script o modulo

Ora lancia util.py in due modi. Prima come script, con il percorso. Su Windows:

python app\util.py

Su macOS e Linux:

python3 app/util.py
app/util.py -> __name__ = __main__
app/util.py -> __package__ = None
app/util.py -> guardia superata: Ciao, Alex!

Poi come modulo, con -m e il nome con i punti, senza .py. Su Windows:

python -m app.util

Su macOS e Linux:

python3 -m app.util
app/__init__.py -> __name__ = app
app/util.py -> __name__ = __main__
app/util.py -> __package__ = 'app'
app/util.py -> guardia superata: Ciao, Alex!

Il __name__ è lo stesso, __main__, e la guardia scatta in entrambi i casi: la documentazione di Python lo dice, perché anche il modulo lanciato con -m è il «top-level code environment». Cambiano due cose. Con -m Python ha eseguito prima app/__init__.py, e __package__ vale 'app' anziché None.

Cassetto di legno aperto con il numero 181, pieno di vecchi fogli, in un mobile d’archivio
Un cassetto si trova con il suo numero completo, non con la sola etichetta: per un modulo il «numero» è il percorso con i punti. Foto: T6 Adventures · Pexels

Un terzo valore cambia, ed è quello che spiega gli errori della sezione dopo: da dove Python cerca i moduli. Con il piccolo file app/percorso.py, che stampa soltanto sys.path[0]:

import sys

print("app/percorso.py -> sys.path[0] =", sys.path[0])
app/percorso.py -> sys.path[0] = /workspace/lab/app

Questo è l’output di python3 app/percorso.py: con un file, Python mette in cima a sys.path la cartella del file. Con python3 -m app.percorso, dalla cartella lab, l’output è stato /workspace/lab: con -m ci mette la cartella corrente, come scrive la documentazione dell’interprete. È la stessa differenza di __package__, vista da un altro lato.

Come lo lanci (da lab) Nome del modulo __package__ sys.path[0]
python3 app/util.py __main__ None la cartella app
python3 -m app.util __main__ 'app' la cartella lab
python3 -c "import app.util" app.util 'app' la cartella corrente (nella prova, la stringa vuota '')

Perché l’import relativo si rompe

app/cli.py usa un import relativo: from . import util, dove il punto significa «nel mio stesso package». Per risolverlo, Python deve sapere a quale package appartiene il file, e lo legge da __package__. Lanciato come script, cli.py ha __package__ uguale a None:

python3 app/cli.py
Traceback (most recent call last):
  File "/workspace/lab/app/cli.py", line 1, in <module>
    from . import util
ImportError: attempted relative import with no known parent package
Terminale Linux nella cartella lab con tre comandi uno sotto l’altro: python3 app/util.py (__name__ = __main__, __package__ = None), python3 -m app.util (__package__ = 'app') e python3 app/cli.py, che termina con ImportError: attempted relative import with no known parent package
Terminale Linux: lo stesso package lanciato in tre modi. Con il percorso util.py ha __package__ uguale a None e cli.py si ferma con l’ImportError; con -m __package__ vale 'app'. Screenshot: ai riparo

Lanciato come modulo, funziona:

python3 -m app.cli
app/__init__.py -> __name__ = app
app/util.py -> __name__ = app.util
app/util.py -> __package__ = 'app'
app/cli.py -> __name__ = __main__
Ciao, Lettore!

Il caso opposto ti coglie quando usi un import assoluto. Con from app import util in app/cli_assoluto.py, lanciato con il percorso, la cartella di ricerca è lab/app e dentro non c’è nessun app:

Traceback (most recent call last):
  File "/workspace/lab/app/cli_assoluto.py", line 1, in <module>
    from app import util
ModuleNotFoundError: No module named 'app'

Con python3 -m app.cli_assoluto, dalla cartella lab, lo stesso file ha stampato Ciao, assoluto!. Il confronto tra import relativi e assoluti merita una guida a parte, e qui ti basta la regola: dentro un package, lancia con -m.

💡 Consiglio pratico — La cartella da cui lanci conta: python -m app.cli si lancia dalla cartella che contiene app. Nella prova, da dentro lab/app, lo stesso comando ha risposto Error while finding module specification for 'app.cli' (ModuleNotFoundError: No module named 'app').

La cartella da cui lanci cambia anche il nome

Il nome completo dipende dal punto da cui parte la ricerca. Dalla cartella che sta sopra lab (nella prova, /workspace), con lab che non ha un __init__.py:

python3 -m lab.app.cli
app/__init__.py -> __name__ = lab.app
app/util.py -> __name__ = lab.app.util
app/util.py -> __package__ = 'lab.app'
app/cli.py -> __name__ = __main__
Ciao, Lettore!

Gli stessi file che prima si chiamavano app e app.util ora si chiamano lab.app e lab.app.util. Il comando ha funzionato anche senza un __init__.py in lab. La guardia ha fatto il suo lavoro e il codice non è cambiato di una riga: è cambiato il punto da cui Python ha risalito la cartella.

Un package che si lancia da solo: __main__.py

Se nel package c’è un file __main__.py, si può lanciare il package intero. Nel laboratorio app/__main__.py contiene due righe: importa cli e stampa il proprio __name__.

python3 -m app
app/__init__.py -> __name__ = app
app/util.py -> __name__ = app.util
app/util.py -> __package__ = 'app'
app/cli.py -> __name__ = app.cli
app/__main__.py -> __name__ = __main__

Qui cli.py ha __name__ uguale a app.cli, perché è stato importato; il file lanciato è __main__.py. È il comportamento descritto nella documentazione di Python, che per questo file consiglia di tenerlo corto e di non metterci la guardia.

Errori tipici

  • ImportError: attempted relative import with no known parent package → hai lanciato con il percorso (python3 app/cli.py) un file che usa from . import ...; __package__ vale None → lancia con python3 -m app.cli, dalla cartella che contiene app.
  • ModuleNotFoundError: No module named 'app' con un import assoluto → con il percorso Python cerca da lab/app, dove app non c’è → lancia con -m dalla cartella lab, oppure metti il punto d’ingresso fuori dal package: nella prova un avvia.py in lab, con from app import util, ha funzionato.
  • Error while finding module specification for 'app.cli' → hai lanciato -m dalla cartella sbagliata (nella prova, da dentro lab/app) → torna nella cartella che contiene app e riprova.
  • Il nome del modulo non è quello che ti aspetti nei log → dipende dal punto da cui lanci (app.util o lab.app.util nella prova) → scegli una cartella di lancio e usa sempre quella.
  • Il print di __init__.py compare prima di tutto → Python esegue __init__.py prima del modulo richiesto → è normale; tieni __init__.py quasi vuoto.

Domande Frequenti (FAQ)

Dentro un package __name__ è sempre package.modulo?

No. È così quando il file viene importato. Se lo lanci come programma, con il percorso o con -m, __name__ vale __main__, come mostra la tabella.

Serve per forza un __init__.py in ogni cartella?

Nella prova lab non lo aveva e python3 -m lab.app.cli ha funzionato; app ne aveva uno, ed è stato eseguito per primo. Non ho provato un app senza __init__.py. Mettilo nelle cartelle che sono package: è il modo più chiaro per dichiararlo.

Posso usare -m anche con un file piatto?

Sì. python3 -m saluta ha stampato __name__ = __main__ e la guardia è scattata; in quel caso __package__ valeva ''. Dentro un package -m è necessario per gli import relativi; con un file piatto è solo un altro modo di avviarlo.

Perché __package__ è None in un caso e '' in un altro?

È quello che ho osservato: None per un file lanciato con il percorso, '' per un file importato o lanciato con -m dalla sua cartella. In entrambi i casi vuol dire «nessun package»; quello che conta è se contiene un nome come app.

Windows e macOS si comportano allo stesso modo?

Non l’ho verificato, perché ho eseguito i comandi solo su Debian. -m è un’opzione dell’interprete e il nome del modulo usa i punti, non le barre; sul percorso del file Windows usa la barra rovesciata (app\util.py).

Dal file piatto al package

La regola è breve: dentro un package lanci con -m, dalla cartella che lo contiene. Per ripassare i due valori di base torna a Capire if __name__ == "__main__" con esempi; per decidere dove mettere cartelle e file c’è la struttura minima di un progetto Python, e per i comandi da terminale la guida ai comandi minimi. Se hai un progetto da riordinare in package e vuoi un punto di partenza che funzioni, è il tipo di lavoro che realizzo su richiesta.

Alex Lunalbi

Alex Lunalbi

Security & privacy

Dona su PayPal

Scrivo di sicurezza digitale pratica e codice IA. Su ai riparo ogni procedura è verificata su macchine reali.

Tag
  • python,
  • moduli,
  • package,
  • import