Moduli e import
Usare da un altro file un modulo che hai scritto tu,cosa fa davvero un import, perché serve `if __name__ == "__main__"`, la libreria standard, e il rischio di nascondere un modulo dietro un file tuo.
Come vuoi imparare?
Il problema che stiamo risolvendo
Nel capitolo scorso abbiamo scritto una funzione line_total. Ora un secondo programma ha bisogno della stessa cosa.
La via più semplice è copiarla. Funziona, il primo giorno.
Poi l'aliquota cambia. Modifichi un file e ti dimentichi dell'altro. Ora due programmi mostrano due totali diversi per lo stesso ordine, e non c'è modo di dire quale sia giusto — perché sono giusti entrambi, secondo il proprio file.
Il problema non è la fatica di copiare. Il problema è che la verità ora vive in due posti.
import pricing
print(pricing.TAX_RATE)
print(pricing.line_total(15.0, 3))0.15
51.75Un solo file, e quanti programmi vuoi che lo usino. Quando l'aliquota cambia, cambia in un solo posto.
Questo capitolo parla di quella riga — import — e di ciò che succede davvero dietro di lei.
Alla fine di questo capitolo sarai in grado di
- Importare un file che hai scritto da un altro file
- Dire cosa distingue
import x,from x import yeimport x as z - Dire cosa succede nel momento in cui avviene un import, e quante volte succede
- Spiegare perché si scrive
if __name__ == "__main__": - Importare moduli dalla libreria standard
- Sapere dove Python cerca, ed evitare di nascondere un modulo dietro un file tuo
Prerequisiti: Scope e predefiniti mutabili.
Un modulo è solo un file
Un file chiamato pricing.py è un modulo chiamato pricing. Non serve dichiarare nulla e non va scritta nessuna riga speciale — basta avere il file.
import pricing porta dentro l'intero modulo, e le cose al suo interno si raggiungono con un punto: pricing.TAX_RATE, pricing.line_total(...).
Il punto può sembrare una battitura in più, ma ha un compito — quando si legge, dice da dove arriva la cosa. In un file grande aiuta molto.
Importare solo ciò che serve
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)51.75
NameError: name 'TAX_RATE' is not definedfrom ... import ... porta dentro solo il nome richiesto, e quel nome si usa direttamente — senza punto.
(Prima della riga dell'errore Python stampa anche alcune righe di traceback; in questo capitolo di solito si mostra solo l'ultima riga.)
Ma gli altri nomi non arrivano. TAX_RATE non è stato richiesto, quindi non è qui, anche se si trova nello stesso file.
Quale scegliere? Più o meno: from quando ti servono due o tre nomi, l'intero modulo quando ne servono molti. E una cosa da non fare mai — from pricing import *. Porta dentro ogni nome in silenzio, e dopo non c'è modo di sapere da dove arrivi line_total.
Rinominare all'importazione
import pricing as p
print(p.line_total(15.0, 3))51.75as dà al modulo un nome più corto dentro questo file.
Usalo quando il nome è davvero lungo, o quando esiste una convenzione comune. Un nome di una lettera per pigrizia butta via la leggibilità che il punto garantiva — nessuno che legga p.line_total(...) può dire da dove arriva.
Importare significa eseguire il file
Questa è la parte che la maggior parte delle persone scopre dopo, di solito nel momento peggiore.
tools.py:
print("tools is being set up")
def ping():
return "pong"main.py:
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")main starts
tools is being set up
pong
main endsDue cose da notare.
Importare significa eseguire il file, dall'alto in basso. Le righe def creano funzioni, e tutto il resto — come quel print — succede davvero. Metti un print o un input al primo livello di un modulo e finirà su chiunque lo importi.
Il secondo import tools non ha fatto nulla. Un modulo viene eseguito una volta per programma; dopo, Python restituisce quello che ha già. Quindi importare lo stesso modulo in dieci punti diversi è sicuro e non costa nulla.
__name__ e quella famosa riga
Ogni modulo ha al suo interno un nome chiamato __name__, che Python imposta da solo.
tools.py:
def ping():
return "pong"
print("module name is", __name__)
if __name__ == "__main__":
print("running directly")Eseguito come python tools.py:
module name is __main__
running directlyMa import tools da un altro file:
module name is toolsLa riga running directly questa volta non è comparsa, perché la condizione era falsa.
La regola è semplice: il file che stai eseguendo ha __name__ uguale a "__main__"; i file importati hanno il proprio nome.
Quindi tutto ciò che sta dentro if __name__ == "__main__": viene eseguito solo quando il file è eseguito direttamente, mai quando è importato.
Perché serva lo si capisce dalla sezione precedente. Importare significa eseguire il file — quindi se main() sta alla fine di un file, chiunque lo importi esegue all'improvviso anche il tuo programma. Quella riga chiude la porta.
La libreria standard
Moltissimi moduli sono distribuiti insieme a Python stesso. Non serve installare nulla.
import math
import statistics
print(math.sqrt(144))
print(math.floor(3.7), math.ceil(3.2))
print(statistics.mean([10, 20, 60]))12.0
3 4
30Un altro che capita spesso:
import random
random.seed(7)
print(random.randint(1, 100))
print(random.choice(["pen", "bag", "ink"]))42
penSenza random.seed(7) ogni esecuzione darebbe numeri diversi — che, in fondo, è proprio il punto. Impostare un seed fissa la sequenza, quindi con la stessa versione di Python l'output qui sopra sarà uguale anche sulla tua macchina. Per scrivere test questo è molto importante: per testare qualcosa di casuale, devi prima renderlo ripetibile.
Non è "casuale", è "dall'aspetto casuale".randomproduce numeri tramite un calcolo, quindi lo stesso seed dà sempre la stessa sequenza. Non usarlo per password o token — per questo esiste il modulosecrets.
Dove Python cerca
Per import pricing, Python cerca in ordine: prima i moduli integrati nell'interprete, poi la cartella dello script che viene eseguito, poi la libreria standard, infine i pacchetti installati.
Questo ordine ha una conseguenza, e fa cadere in trappola.
Nella tua cartella c'è un file chiamato random.py, e scrivi:
import random
print(random.choice([1, 2, 3]))AttributeError: module 'random' has no attribute 'choice'Il tuo file viene prima del vero random, quindi è quello importato — e lì dentro non c'è nessun choice.
Il messaggio confonde, perché dice che random non ha choice, cosa che sembra impossibile. Ma sta parlando del tuo random.
La regola: non dare mai a un file il nome di un modulo. random.py, string.py, json.py, csv.py — sembrano nomi innocenti, ed è esattamente per questo che la trappola funziona.
Un'eccezione, che spiega la regola. In molte installazioni (per esempio nel sistema Linux su cui sono stati eseguiti gli esempi di questo capitolo), se crei un file chiamatomath.pye scriviimport math, ottieni comunque quello vero, non il tuo, perché in quel casomathè integrato nell'interprete, e i moduli integrati vengono prima nell'ordine di ricerca. Quali moduli sono integrati dipende da come è stato compilato Python. Quindi la trappola scatta per alcuni nomi e per altri no — il che è più difficile da ricordare che semplicemente rinominare il file.
Organizzare i file in cartelle
Man mano che i file aumentano, si possono raggruppare in cartelle. Metti un file __init__.py dentro la cartella — va bene anche vuoto — e la cartella diventa un pacchetto.
shop/
__init__.py
pricing.py
main.pyIl percorso si può poi scrivere con dei punti:
from shop.pricing import line_total
print(line_total(15.0, 3))51.75Per ora basta così. Per il resto di questo capitolo restiamo con file piatti.
Un esempio completo
Tre file, con le dipendenze che vanno in una sola direzione.
pricing.py:
"""Prices and tax. Nothing here prints, and nothing here asks for input."""
TAX_RATE = 0.15
def line_total(price, quantity):
"""One line of an order, tax included."""
return round(price * quantity * (1 + TAX_RATE), 2)
def order_total(lines):
"""`lines` is a list of (name, price, quantity) tuples."""
return round(sum(line_total(price, qty) for _, price, qty in lines), 2)report.py:
"""Turning numbers into lines of text. It imports pricing; pricing imports nothing."""
import pricing
def receipt(lines):
rows = [f"{name:<8} {pricing.line_total(price, qty):>8.2f}"
for name, price, qty in lines]
rows.append("-" * 17)
rows.append(f"{'total':<8} {pricing.order_total(lines):>8.2f}")
return rows
if __name__ == "__main__":
print("report.py has no data of its own to show")main.py:
"""The only file that is meant to be run."""
import statistics
from report import receipt
ORDER = [
("pen", 15.0, 3),
("bag", 850.0, 1),
("ink", 120.0, 2),
]
def main():
for row in receipt(ORDER):
print(row)
print()
quantities = [qty for _, _, qty in ORDER]
print("mean quantity:", statistics.mean(quantities))
if __name__ == "__main__":
main()Eseguito con python main.py:
pen 51.75
bag 977.50
ink 276.00
-----------------
total 1305.25
mean quantity: 2Quattro cose su cui vale la pena soffermarsi.
Le dipendenze vanno in una direzione sola. main conosce report, report conosce pricing, e pricing non conosce nessuno. Quest'ordine crea una catena, e il file in fondo — pricing — si può testare completamente da solo. Se pricing avesse dovuto importare report, nessuno dei due potrebbe più stare da solo.
pricing.py non stampa nulla e non chiede nulla. Riceve valori e restituisce valori — le funzioni pure del capitolo ventuno. Dato che importare significa eseguire il file, questa sobrietà non è una questione di cortesia, è un requisito.
report.py ha anche un if __name__ == "__main__":, e non fa nulla di utile — il file non ha dati propri, quindi non ha nulla da mostrare. Si potrebbe anche toglierlo. È lì perché, se il file viene mai eseguito direttamente, dica almeno qualcosa, invece di non fare nulla in silenzio.
Nessun file tranne main.py fa qualcosa da solo. L'intero programma ha esattamente un punto di ingresso, e leggendolo si capisce cosa succederà.
Quando si rompe
ModuleNotFoundError: No module named 'priceing' Un errore di battitura, oppure il file si trova in un'altra cartella. Controlla che sia accanto allo script che stai eseguendo.
ImportError: cannot import name 'line_totl' from 'pricing' Il modulo è stato trovato, il nome al suo interno no. Il messaggio finisce con il percorso completo del file del modulo tra parentesi, e spesso suggerisce il nome più vicino — Did you mean: 'line_total'?
AttributeError: module 'random' has no attribute 'choice' Quasi sempre un file tuo che nasconde il modulo vero. Cerca quel nome nella tua cartella e rinominalo. Basta questo — una volta che il file .py è sparito, Python non usa più la vecchia copia rimasta in __pycache__.
ImportError: cannot import name 'rows' from partially initialized module 'table' (most likely due to a circular import) Due file si importano a vicenda, e almeno uno dei due usa from ... import ... per chiedere un nome che nell'altro file non esiste ancora — perché quel file non ha ancora finito di essere eseguito dall'alto in basso. La soluzione di solito è spostare ciò che entrambi richiedono in un terzo file che non importi nessuno dei due.
Qualcosa è stato stampato, o ha chiesto un input, appena l'ho importato C'è del codice al primo livello del modulo. Mettilo dentro una funzione e chiamala da dentro if __name__ == "__main__":.
Ho modificato il file ma vedo ancora il vecchio comportamento Esegui di nuovo il programma — un programma in esecuzione non rilegge il modulo. In un notebook, riavvia il kernel.
Passo 4 di 6 — Prevedi
Verifica cosa hai capito
È stato richiesto line_total, ma non TAX_RATE. Cosa succede?
# pricing.py
TAX_RATE = 0.15
def line_total(price, quantity):
return round(price * quantity * (1 + TAX_RATE), 2)
# main.py
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)- A`51.75` stampa, poi `None`
- BStampano sia `51.75` sia `0.15`
- C`51.75` stampa, poi un `NameError`
- DUn `ImportError`
import tools è scritto due volte. Cosa viene stampato?
# tools.py
print("tools is being set up")
def ping():
return "pong"
# main.py
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")- Atools is being set up main starts pong main ends
- Bmain starts pong main ends
- Cmain starts tools is being set up pong main ends
- Dmain starts tools is being set up tools is being set up pong main ends
Si esegue python main.py. Cosa viene stampato?
# tools.py
print("name:", __name__)
if __name__ == "__main__":
print("direct")
# main.py
import tools
print("done")- Aname: tools done
- Bname: main done
- Cname: __main__ direct done
- Dname: tools direct done
Per rispondere serve un account
Accedi per verificare le tue risposte
Le domande sono qui sopra, e ragionarci a mente è la parte che conta. Accedi per vedere le risposte, le spiegazioni e i suggerimenti a tre livelli.
Tocca a te
Crea tre file nella stessa cartella.
textutil.py — due funzioni: initials(name) che trasforma "rafi ahmed" in "RA", e shorten(text, limit) che taglia i testi lunghi e aggiunge ... alla fine. Nessun print in questo file.
table.py — importa textutil, e offre rows(people), che riceve una lista di nomi e restituisce una lista di righe formattate.
main.py — importa table, la chiama con alcuni nomi, e stampa. Metti if __name__ == "__main__": in tutti e tre i file.
Poi quattro esperimenti, tutti da vedere con i tuoi occhi:
- Aggiungi
print("loaded")in cima atextutil.pyed eseguipython main.py. La riga compare? Prima o dopo il primoprintdimain.py? - Scrivi
import tabledue volte inmain.py. Quante volte stampaloaded? - Crea nella cartella un file vuoto chiamato
statistics.py, poi inmain.pyimportastatisticse chiamastatistics.mean([1, 2]). Leggi il messaggio — di qualestatisticssta parlando? - Aggiungi
from table import rowsproprio in cima atextutil.py, in modo che i due si importino a vicenda, poi eseguipython main.py. L'ultima riga del messaggio inizierà così:ImportError: cannot import name 'rows' from partially initialized module 'table'. (Un sempliceimport tablenon dà errore: in quel casotextutilottiene solo un nome per il modulo a metà costruito, erowsviene cercato più tardi, quando una funzione viene eseguita.from table import rowschiede il nome subito, mentretable.pyè ancora fermo alla sua prima riga.)
Gli ultimi due sono errori che possono costare ore se il messaggio non è familiare, e pochi secondi una volta che li hai causati tu stesso.
Passo 6 di 6
Sfida — il quiz del capitolo
Dieci domande dalla più facile alla più difficile. Le ultime sono difficili di proposito.
Accedi per fare il quiz