Moduli e import: organizzare programmi più grandi

Bentornato.
Nella lezione precedente hai imparato la gestione degli errori.
I tuoi programmi hanno imparato a sopravvivere a input sbagliati, file mancanti ed esplosioni drammatiche di Python.
Hai scritto cose come:
try:
age = int(input("Age: "))
except ValueError:
print("Please enter a valid age.")
Molto bene.
Ora i tuoi programmi sono più sicuri.
Non svengono subito quando qualcuno scrive:
banana
Ottimo progresso.
Ma ora abbiamo un altro problema.
I tuoi programmi stanno diventando più grandi.
All’inizio bastava un file.
Qualcosa tipo:
main.py
Bello.
Semplice.
Amichevole.
Poi hai aggiunto funzioni.
Poi liste.
Poi dizionari.
Poi file.
Poi gestione degli errori.
E all’improvviso main.py diventa enorme.
Un file gigante.
Una lasagna di codice.
Un armadio digitale dove c’è tutto dentro, ma nessuno sa dove siano i calzini.
Qui entrano in scena moduli e import.
I moduli ti permettono di dividere il codice in più file.
Gli import ti permettono di riutilizzare codice da un file dentro un altro file.
Invece di un file gigante, puoi organizzare il progetto così:
main.py
helpers.py
tasks.py
calculator.py
Molto più pulito.
Molto più professionale.
Molto meno “dove si nasconde quella funzione?”
Cosa imparerai
In questa lezione imparerai:
- cos’è un modulo;
- perché i moduli sono utili;
- come creare il tuo modulo;
- come usare
import; - come usare
from ... import; - come importare più funzioni;
- come organizzare funzioni helper;
- come evitare che il codice del modulo venga eseguito per sbaglio;
- cosa significa
__name__ == "__main__"; - come dividere un task manager in più file;
- errori comuni dei principianti con gli import.
Alla fine di questa lezione il tuo codice sarà più organizzato.
Sarai capace di dividere programmi più grandi in file più piccoli.
Riutilizzerai funzioni invece di copiarle.
Renderai i tuoi progetti più facili da leggere.
E il tuo io futuro soffrirà meno.
Obiettivo nobile.
Cos’è un modulo?
Un modulo è semplicemente un file Python.
Sì.
Davvero.
Se hai un file chiamato:
helpers.py
Quel file è un modulo.
Se hai un file chiamato:
calculator.py
Quel file è un modulo.
Un modulo può contenere:
- variabili;
- funzioni;
- liste;
- dizionari;
- classi;
- codice.
Per ora useremo soprattutto i moduli per salvare funzioni.
Esempio:
helpers.py
def greet_user(name):
print(f"Hello, {name}!")
Poi un altro file può importare e usare questa funzione.
Questa è l’idea principale.
Scrivi codice in un file.
Usalo in un altro file.
Molto utile.
Molto pulito.
Molto Python.
Perché usare i moduli?
I moduli aiutano a organizzare il codice.
Invece di un file enorme, puoi dividere il programma in parti più piccole.
Esempio:
main.py avvia il programma
helpers.py funzioni helper generali
tasks.py funzioni legate ai task
contacts.py funzioni legate ai contatti
Questo rende il progetto più facile da capire.
Un file gigante è difficile da leggere.
Un progetto con file chiari è più facile da navigare.
Pensa ai moduli come cassetti.
Un cassetto per i calzini.
Un cassetto per le magliette.
Un cassetto per i cavi misteriosi che ti rifiuti di buttare.
Anche il codice ha bisogno di cassetti.
Senza organizzazione, tutto diventa caos.
E il caos ha già abbastanza lavoro.
Il tuo primo modulo
Crea una cartella:
python_modules_lesson
Dentro crea due file:
main.py
helpers.py
La cartella dovrebbe apparire così:
python_modules_lesson/
├── helpers.py
└── main.py
In helpers.py, scrivi:
def greet_user(name):
print(f"Hello, {name}!")
In main.py, scrivi:
import helpers
helpers.greet_user("Anna")
Esegui:
python main.py
oppure:
python3 main.py
Output:
Hello, Anna!
Congratulazioni.
Hai creato il tuo modulo.
Molto elegante.
Molto organizzato.
Molto “guarda, il mio codice ora ha stanze separate”.
Come funziona import
Questa riga importa il modulo:
import helpers
Dice a Python:
Carica il file helpers.py così posso usare quello che c’è dentro.
Poi puoi chiamare la funzione così:
helpers.greet_user("Anna")
Perché helpers.?
Perché la funzione vive dentro il modulo helpers.
Il nome completo è:
helpers.greet_user
Questo è utile perché mostra da dove arriva la funzione.
Codice chiaro.
Niente indovinelli.
Niente lavoro da detective.
Niente “quale file ha creato questa funzione?”
Python te lo mostra.
Importare con from
C’è anche un altro modo.
Invece di importare tutto il modulo, puoi importare direttamente una funzione.
In main.py, scrivi:
from helpers import greet_user
greet_user("Anna")
Output:
Hello, Anna!
Ora non ti serve:
helpers.greet_user()
Puoi chiamare:
greet_user()
perché hai importato direttamente la funzione.
Entrambi gli stili funzionano.
Stile 1:
import helpers
helpers.greet_user("Anna")
Stile 2:
from helpers import greet_user
greet_user("Anna")
Entrambi sono utili.
Il primo stile è più chiaro su dove nasce la funzione.
Il secondo stile è più corto.
La programmazione è piena di compromessi.
Come la vita.
Ma con più parentesi.
Importare più funzioni
In helpers.py, scrivi:
def greet_user(name):
print(f"Hello, {name}!")
def say_goodbye(name):
print(f"Goodbye, {name}!")
In main.py, scrivi:
from helpers import greet_user, say_goodbye
greet_user("Anna")
say_goodbye("Anna")
Output:
Hello, Anna!
Goodbye, Anna!
Puoi importare più funzioni dallo stesso modulo.
Separale con virgole:
from helpers import greet_user, say_goodbye
Questo è utile quando un modulo ha varie funzioni helper.
Ma non importare troppe cose alla cieca.
Se la riga di import diventa molto lunga, forse il modulo sta facendo troppo.
O forse il progetto sta crescendo.
Entrambe le possibilità meritano attenzione.
Importare tutto
Potresti vedere questo:
from helpers import *
Questo importa tutto da helpers.py.
Funziona.
Ma i principianti dovrebbero fare attenzione.
Perché?
Perché diventa poco chiaro da dove arrivano le cose.
Esempio:
from helpers import *
greet_user("Anna")
say_goodbye("Anna")
Funziona.
Ma quando il progetto cresce, può diventare confuso.
Da dove arriva greet_user()?
Da dove arriva say_goodbye()?
Quale modulo?
Quale file?
Quale cassetto?
Mistero.
Di solito è meglio essere espliciti:
from helpers import greet_user, say_goodbye
Chiaro è meglio di magico.
La magia è bella nei film.
Meno bella nel debugging.
Nomi dei moduli
I nomi dei moduli dovrebbero essere semplici.
Buoni:
helpers.py
tasks.py
contacts.py
calculator.py
file_utils.py
Cattivi:
my helper functions.py
task-manager.py
123tasks.py
import.py
Usa lettere minuscole e underscore.
Buoni:
file_utils.py
safe_input.py
task_manager.py
Evita spazi.
Evita trattini.
Evita nomi che sono keyword di Python.
Per esempio, non chiamare il file:
import.py
È cercarsi problemi.
Python usa già import.
Non confondere Python.
È potente, ma non è il tuo psicologo.
Un modulo per funzioni di calcolatrice
Crea i file:
main.py
calculator.py
In calculator.py, scrivi:
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
return a / b
In main.py, scrivi:
import calculator
print(calculator.add(5, 3))
print(calculator.subtract(5, 3))
print(calculator.multiply(5, 3))
print(calculator.divide(10, 2))
Output:
8
2
15
5.0
Ora la logica della calcolatrice vive in:
calculator.py
E il programma parte da:
main.py
Questo è più pulito.
Il file principale non deve contenere ogni funzione.
Può usare funzioni da altri moduli.
Così crescono i progetti.
Lentamente.
Poi all’improvviso.
Poi ti servono cartelle.
Ma non ancora.
Un mostro alla volta.
Un modulo per input sicuro
Nella lezione precedente abbiamo scritto funzioni per input sicuro.
Spostiamole in un modulo.
Crea:
safe_input.py
Scrivi:
def get_integer(prompt):
while True:
try:
value = int(input(prompt))
return value
except ValueError:
print("Please enter a valid whole number.")
def get_float(prompt):
while True:
try:
value = float(input(prompt))
return value
except ValueError:
print("Please enter a valid number.")
Ora in main.py, scrivi:
from safe_input import get_integer, get_float
price = get_float("Price: ")
quantity = get_integer("Quantity: ")
total = price * quantity
print(f"Total: {total:.2f}")
Questo è molto bello.
La logica dell’input sicuro è riutilizzabile.
Qualsiasi programma può importarla.
Non serve copiare e incollare le stesse funzioni ancora e ancora.
Questo è progresso.
Questa è organizzazione.
Questa è meno sofferenza.
Ottimo.
Importare moduli della standard library
Python arriva già con molti moduli.
Questa si chiama standard library.
Esempi:
math
random
datetime
os
pathlib
json
Puoi importare anche questi.
Esempio con math:
import math
print(math.sqrt(16))
Output:
4.0
Esempio con random:
import random
number = random.randint(1, 10)
print(number)
Questo stampa un numero casuale tra 1 e 10.
Python ti dà tanti strumenti gratis.
Non devi scrivere tutto da solo.
Molto bello.
Molto generoso.
Molto “per favore usa la cassetta degli attrezzi prima di costruire un martello da zero”.
Esempio: random choice
Crea un file:
random_example.py
Scrivi:
import random
names = ["Anna", "Marco", "Sofia", "Luca"]
chosen_name = random.choice(names)
print(f"Chosen name: {chosen_name}")
Output possibile:
Chosen name: Sofia
Oppure:
Chosen name: Marco
Perché è casuale.
Il modulo random ha funzioni utili come:
random.randint(1, 10)
random.choice(names)
Questo è utile per:
giochi
esercizi casuali
dati di test
piccoli esperimenti
La casualità è utile.
Anche pericolosa.
Specialmente quando nomini i file.
Non dare nomi casuali ai file di produzione.
Il tuo io futuro piangerà.
Esempio: modulo math
Crea:
math_example.py
Scrivi:
import math
radius = 5
area = math.pi * radius ** 2
print(f"Area: {area:.2f}")
Output:
Area: 78.54
Il modulo math ci dà:
math.pi
math.sqrt()
math.floor()
math.ceil()
Molto utile per i calcoli.
Non devi definire pi da solo.
Non scrivere:
pi = 3.14
a meno che tu voglia davvero una piccola approssimazione con scarpe economiche.
Usa:
math.pi
Molto meglio.
Codice che gira quando viene importato
Importante.
Molto importante.
Crea helpers.py:
def greet_user(name):
print(f"Hello, {name}!")
print("This is helpers.py")
Crea main.py:
import helpers
helpers.greet_user("Anna")
Esegui main.py.
Output:
This is helpers.py
Hello, Anna!
Perché Python ha stampato:
This is helpers.py
Perché quando importi un modulo, Python esegue il codice top-level di quel file.
Questo significa che il codice fuori dalle funzioni può partire durante l’import.
Questo sorprende molto i principianti.
Molto.
Quindi fai attenzione.
Un modulo di solito dovrebbe contenere funzioni e costanti.
Non esecuzione casuale del programma.
Altrimenti importare il modulo diventa piccante.
E non in senso buono.
Il pattern name == "main"
A volte vuoi che del codice venga eseguito solo quando il file viene lanciato direttamente.
Non quando viene importato.
Python ha un pattern comune per questo:
if __name__ == "__main__":
code_here
Esempio:
def greet_user(name):
print(f"Hello, {name}!")
if __name__ == "__main__":
greet_user("Test User")
Ora:
- se esegui
helpers.pydirettamente, stampaHello, Test User!; - se importi
helpers.py, quel codice di test non parte.
Questo è molto utile.
Ti permette di testare un modulo senza creare sorprese quando lo importi.
All’inizio questa riga sembra strana:
if __name__ == "__main__":
Non andare in panico.
In pratica significa:
Esegui questo codice solo se questo file è il file principale eseguito.
Sembra strano.
Molto utile.
Molto Python.
Testare un modulo in modo sicuro
Crea helpers.py:
def greet_user(name):
return f"Hello, {name}!"
if __name__ == "__main__":
print(greet_user("Anna"))
Se esegui:
python helpers.py
Output:
Hello, Anna!
Ora crea main.py:
from helpers import greet_user
message = greet_user("Marco")
print(message)
Esegui:
python main.py
Output:
Hello, Marco!
Nota che il codice di test dentro:
if __name__ == "__main__":
non è partito quando il modulo è stato importato.
Perfetto.
Pulito.
Professionale.
Meno sorprese.
E nella programmazione, meno sorprese di solito è bene.
Tranne al compleanno.
Ma anche lì, non in produzione.
Dividere un programma in file
Organizziamo un piccolo progetto.
Crea questa struttura:
task_project/
├── main.py
├── tasks.py
└── safe_input.py
In safe_input.py, scrivi:
def get_integer(prompt):
while True:
try:
value = int(input(prompt))
return value
except ValueError:
print("Please enter a valid whole number.")
In tasks.py, scrivi:
FILE_NAME = "tasks.txt"
def load_tasks():
tasks = []
try:
with open(FILE_NAME, "r") as file:
for line in file:
tasks.append(line.strip())
except FileNotFoundError:
pass
return tasks
def save_tasks(tasks):
with open(FILE_NAME, "w") as file:
for task in tasks:
file.write(task + "\n")
def add_task(tasks):
task = input("Task: ")
tasks.append(task)
save_tasks(tasks)
print("Task saved.")
def show_tasks(tasks):
if len(tasks) == 0:
print("No tasks yet.")
else:
print("Tasks:")
for index, task in enumerate(tasks, start=1):
print(f"{index}. {task}")
def remove_task(tasks, task_number):
index = task_number - 1
if index < 0 or index >= len(tasks):
print("Invalid task number.")
return
removed_task = tasks.pop(index)
save_tasks(tasks)
print(f"Removed: {removed_task}")
In main.py, scrivi:
from safe_input import get_integer
from tasks import load_tasks, add_task, show_tasks, remove_task
def show_menu():
print("----- Task Manager -----")
print("1. Add task")
print("2. Show tasks")
print("3. Remove task")
print("q. Quit")
tasks = load_tasks()
while True:
show_menu()
choice = input("Choose an option: ").lower()
if choice == "1":
add_task(tasks)
elif choice == "2":
show_tasks(tasks)
elif choice == "3":
if len(tasks) == 0:
print("No tasks to remove.")
else:
show_tasks(tasks)
task_number = get_integer("Task number to remove: ")
remove_task(tasks, task_number)
elif choice == "q":
print("Goodbye.")
break
else:
print("Unknown option.")
Questo progetto è molto meglio organizzato.
main.py controlla il flusso del programma.
tasks.py contiene la logica dei task.
safe_input.py contiene la logica dell’input sicuro.
Questo è pulito.
Questo è leggibile.
Questa è struttura da progetto reale.
Piccolo progetto.
Ma struttura reale.
Molto bene.
Perché questa struttura è migliore
Guarda i file:
main.py
tasks.py
safe_input.py
Ogni file ha un compito.
main.py:
Avvia il programma.
Mostra il menu.
Gestisce le scelte dell’utente.
tasks.py:
Carica task.
Salva task.
Aggiunge task.
Mostra task.
Rimuove task.
safe_input.py:
Ottiene input intero sicuro.
Questo si chiama separazione delle responsabilità.
Frase elegante.
Idea semplice:
Non mettere tutto dappertutto.
Una buona organizzazione rende il codice più facile da mantenere.
Quando ti serve la logica dei task, vai in tasks.py.
Quando ti serve la logica dell’input, vai in safe_input.py.
Quando ti serve il flusso del programma, vai in main.py.
Niente caccia al tesoro.
Niente archeologia del codice.
Molto meglio.
Errore comune: ModuleNotFoundError
Potresti vedere:
ModuleNotFoundError
Esempio:
import helpers
Ma Python dice:
ModuleNotFoundError: No module named 'helpers'
Possibili motivi:
helpers.py non esiste
helpers.py è in un’altra cartella
hai scritto male il nome del file
stai eseguendo Python dal posto sbagliato
Per principianti, tieni i file nella stessa cartella.
Esempio:
project/
├── main.py
└── helpers.py
Poi esegui da quella cartella:
python main.py
Semplice.
Meno dramma.
Gli import di Python sono potenti.
Ma i percorsi possono diventare piccanti.
Inizia semplice.
Errore comune: nome file sbagliato
Se il tuo file si chiama:
helper.py
Ma scrivi:
import helpers
Python non lo troverà.
I nomi devono corrispondere.
File:
helpers.py
Import:
import helpers
Niente .py nell’import.
Corretto:
import helpers
Sbagliato:
import helpers.py
Python importa nomi di moduli, non nomi file con estensione.
Piccolo dettaglio.
Grande confusione.
Molto normale.
Errore comune: circular imports
I circular imports succedono quando due file si importano a vicenda.
Esempio:
a.py importa b.py
b.py importa a.py
Questo può creare errori confusi.
I principianti dovrebbero evitarlo.
Regola semplice:
main.py può importare altri moduli.
I moduli helper di solito non dovrebbero importare main.py.
Bene:
main.py importa tasks.py
main.py importa safe_input.py
Rischioso:
tasks.py importa main.py
main.py importa tasks.py
Questo crea un loop.
Un serpente che si mangia la coda.
Simbolo interessante.
Bug fastidioso.
Evita.
Errore comune: mettere logica del programma nei moduli importati
Cattivo helpers.py:
def greet_user(name):
print(f"Hello, {name}!")
name = input("Name: ")
greet_user(name)
Se importi questo modulo, chiede input immediatamente.
Sorpresa.
Non bene.
Meglio:
def greet_user(name):
print(f"Hello, {name}!")
if __name__ == "__main__":
name = input("Name: ")
greet_user(name)
Ora l’input succede solo quando esegui helpers.py direttamente.
Non quando lo importi.
Questo è molto più sicuro.
Ricorda:
I moduli di solito dovrebbero definire cose riutilizzabili.
main.py di solito dovrebbe eseguire il programma.
Ottima regola.
Errore comune: importare troppo
Questo può diventare disordinato:
from helpers import *
from tasks import *
from calculator import *
Ora il tuo namespace è pieno di nomi importati.
Potresti non sapere più da dove arriva qualcosa.
Meglio:
import helpers
import tasks
oppure:
from helpers import greet_user
from tasks import load_tasks, save_tasks
Sii chiaro.
Gli import espliciti rendono il codice più facile da capire.
Il tuo io futuro ama gli import espliciti.
Il tuo io futuro è stanco e non ha pazienza per i misteri.
Rispetta il tuo io futuro.
Pratica
Crea questo progetto:
calculator_project/
├── main.py
└── calculator.py
In calculator.py, crea le funzioni:
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
return a / b
In main.py, importa le funzioni e chiedi all’utente due numeri.
Soluzione esempio:
from calculator import add, subtract, multiply, divide
first_number = float(input("First number: "))
second_number = float(input("Second number: "))
print(f"Add: {add(first_number, second_number)}")
print(f"Subtract: {subtract(first_number, second_number)}")
print(f"Multiply: {multiply(first_number, second_number)}")
if second_number == 0:
print("Cannot divide by zero.")
else:
print(f"Divide: {divide(first_number, second_number)}")
Questa pratica insegna:
- creare un modulo;
- importare funzioni;
- usare funzioni importate;
- separare logica e flusso del programma.
Molto utile.
Molto pulito.
Molto adatto ai principianti.
Mini sfida
Crea questo progetto:
notes_project/
├── main.py
└── notes.py
In notes.py, crea le funzioni:
add_note()
show_notes()
Usa un file chiamato:
notes.txt
Esempio notes.py:
FILE_NAME = "notes.txt"
def add_note():
note = input("Note: ")
with open(FILE_NAME, "a") as file:
file.write(note + "\n")
print("Note saved.")
def show_notes():
try:
with open(FILE_NAME, "r") as file:
print("Notes:")
has_notes = False
for line in file:
print(f"- {line.strip()}")
has_notes = True
if not has_notes:
print("No notes yet.")
except FileNotFoundError:
print("No notes yet.")
Esempio main.py:
from notes import add_note, show_notes
def show_menu():
print("----- Notes App -----")
print("1. Add note")
print("2. Show notes")
print("q. Quit")
while True:
show_menu()
choice = input("Choose an option: ").lower()
if choice == "1":
add_note()
elif choice == "2":
show_notes()
elif choice == "q":
print("Goodbye.")
break
else:
print("Unknown option.")
Questo è un ottimo progetto per principianti.
Usa:
- moduli;
- import;
- funzioni;
- file;
- cicli;
- condizioni;
- gestione degli errori.
Piccola app.
Buona struttura.
Molto utile.
Molto reale.
Sfida extra: dividere la rubrica
Crea:
contact_project/
├── main.py
├── contacts.py
└── safe_input.py
contacts.py dovrebbe contenere:
load_contacts()
save_contacts()
add_contact()
show_contacts()
main.py dovrebbe contenere:
menu
main loop
scelte dell’utente
Questa è una sfida più grande.
Conosci già i pezzi.
Ora l’obiettivo è l’organizzazione.
Non scrivere tutto in un solo file.
Fai in modo che ogni file sia responsabile di una parte.
Così i progetti più grandi restano comprensibili.
O almeno meno terrificanti.
Meno terrificante è bene.
Checklist per principianti
Quando gli import non funzionano, controlla:
I file sono nella stessa cartella?
Ho scritto correttamente il nome del modulo?
Ho scritto l’import senza .py?
Sto eseguendo Python dalla cartella corretta?
Ho creato circular imports?
Ho messo troppo codice eseguibile nel modulo importato?
Questo codice dovrebbe stare dentro if __name__ == "__main__"?
Sto importando troppo con *?
Ogni file ha un compito chiaro?
I problemi con gli import sono comuni.
Molto comuni.
Non andare in panico.
Di solito il problema è:
nome file sbagliato
cartella sbagliata
stile di import sbagliato
codice che parte durante l’import
Sistema con calma.
I computer sono letterali.
Fanno esattamente quello che dici.
Non quello che intendevi.
Fastidioso.
Ma giusto.
Riassunto
Oggi hai imparato:
- un modulo è un file Python;
- i moduli aiutano a organizzare il codice;
import module_nameimporta un modulo intero;from module import functionimporta direttamente una funzione;- puoi importare più funzioni;
- evita
from module import *quando possibile; - i nomi dei moduli dovrebbero essere semplici e minuscoli;
- Python ha moduli della standard library come
matherandom; - il codice top-level di un modulo viene eseguito quando viene importato;
if __name__ == "__main__"evita esecuzione indesiderata durante l’import;- programmi più grandi possono essere divisi in file come
main.py,tasks.pyesafe_input.py; - ogni file dovrebbe avere una responsabilità chiara;
- i circular imports possono causare problemi confusi.
Questo è un passo enorme.
Ora i tuoi progetti possono crescere senza diventare un unico file gigante.
Puoi riutilizzare codice.
Puoi organizzare funzioni.
Puoi separare il flusso del programma dalla logica helper.
Così iniziano i progetti reali.
Non con un file magico che fa tutto.
Ma con piccoli file che lavorano insieme.
Struttura pulita.
Codice riutilizzabile.
Meno caos.
Molto Python.
Molto professionale.
Prossima lezione
Nella prossima lezione impareremo JSON.
JSON è un formato dati molto comune.
Assomiglia molto ai dizionari e alle liste di Python.
Imparerai a salvare dati strutturati come questo:
contact = {
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
}
dentro un file.
E poi caricarli di nuovo più tardi.
Questo è molto importante per applicazioni reali.
I file di testo sono belli.
Ma JSON è migliore per dati strutturati.
È usato in API, file di configurazione, web app e molti progetti reali.
Molto utile.
Molto pratico.
Molto livello successivo.