← Back to course

Moduli e import: organizzare programmi più grandi

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:

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:

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:

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:

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:

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:

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.