← Back to course

Gestione degli errori: aiutare i programmi a sopravvivere

Gestione degli errori: aiutare i programmi a sopravvivere

Bentornato.

Nella lezione precedente hai imparato i file.

I tuoi programmi hanno imparato a salvare dati e leggerli più tardi.

Hai scritto cose come:

with open("tasks.txt", "r") as file:
    content = file.read()

Molto bene.

Ora i tuoi programmi possono ricordare le cose.

Task.

Note.

Contatti.

Piccoli pezzi di memoria digitale.

Bellissimo.

Ma ora abbiamo un altro problema.

I programmi crashano.

Molto.

A volte perché il file manca.

A volte perché l’utente inserisce testo invece di un numero.

A volte perché dividi per zero.

A volte perché il codice aveva un piccolo errore nascosto come un ninja dietro una parentesi.

Esempio:

age = int(input("Age: "))

Se l’utente scrive:

banana

Python dice:

ValueError

E il programma crasha.

Molto teatrale.

Molto Python.

Molto normale.

La gestione degli errori aiuta il tuo programma a sopravvivere ai problemi.

Invece di crashare, il programma può rispondere con calma.

Per esempio:

Please enter a valid number.

Molto meglio.

Molto più professionale.

Molto meno “il programma è esploso perché qualcuno ha scritto banana”.

Cosa imparerai

In questa lezione imparerai:

Alla fine di questa lezione i tuoi programmi saranno più stabili.

Non andranno subito nel panico.

Gestiranno problemi comuni.

Sopravvivranno agli utenti.

E gli utenti sono pericolosi.

Specialmente quando l’utente sei tu a mezzanotte.

Cos’è un errore?

Un errore è un problema che impedisce al programma di funzionare correttamente.

Esempio:

number = int("banana")

Python non può convertire "banana" in un numero.

Quindi dà un errore:

ValueError

Altro esempio:

result = 10 / 0

Python dà:

ZeroDivisionError

Perché dividere per zero non è permesso.

Altro esempio:

with open("missing.txt", "r") as file:
    content = file.read()

Python dà:

FileNotFoundError

Perché il file non esiste.

Gli errori sono normali.

Non sono una prova che sei scarso nella programmazione.

Sono una prova che la programmazione è programmazione.

Benvenuto.

Cos’è un’eccezione?

In Python, molti errori che avvengono mentre il programma gira si chiamano eccezioni.

Un’eccezione succede quando Python incontra un problema durante l’esecuzione del programma.

Esempi:

ValueError
FileNotFoundError
ZeroDivisionError
NameError
TypeError
IndexError
KeyError

Ne hai già visti alcuni.

Forse troppi.

È normale.

Un’eccezione di solito ferma il programma.

A meno che tu non la gestisca.

Gestire gli errori significa:

Se qualcosa va storto, fai qualcosa di sicuro invece di crashare.

A questo servono try e except.

Molto utile.

Molto importante.

Molto “per favore non esplodere”.

Il tuo primo try ed except

Crea un file:

error_handling.py

Scrivi:

try:
    number = int(input("Enter a number: "))
    print(f"You entered: {number}")
except ValueError:
    print("That was not a valid number.")

Eseguilo.

Se inserisci:

25

Output:

You entered: 25

Se inserisci:

banana

Output:

That was not a valid number.

Il programma non crasha.

Ottimo.

Python ha provato a eseguire il codice rischioso.

Quando non ci è riuscito, Python è saltato a except.

Molto civile.

Molto utile.

Molto meglio che urlare in testo rosso.

Come funzionano try ed except

Struttura base:

try:
    risky_code
except SomeError:
    code_to_run_if_error_happens

Esempio:

try:
    age = int(input("Age: "))
    print(age)
except ValueError:
    print("Please enter a number.")

Il blocco try contiene codice che potrebbe fallire.

Il blocco except contiene codice che viene eseguito se succede l’errore selezionato.

Pensalo così:

Prova questo.
Se succede questo problema specifico, fai quest’altra cosa.

È come una rete di sicurezza.

Senza la rete, il programma cade.

Con la rete, il programma dice:

Bel tentativo, input banana.

E continua.

Gestire ValueError

ValueError succede spesso quando converti input.

Esempio:

age = int(input("Age: "))

Se l’utente inserisce:

hello

Python non può convertirlo in un intero.

Quindi genera:

ValueError

Versione più sicura:

try:
    age = int(input("Age: "))
    print(f"You are {age} years old.")
except ValueError:
    print("Age must be a number.")

Questo è molto comune.

L’input utente è pericoloso.

Gli utenti scrivono cose strane.

A volte per sbaglio.

A volte perché stanno testando il tuo programma.

A volte perché sono caos con le dita.

Quindi fai sempre attenzione con l’input utente.

Chiedere di nuovo dopo un errore

Invece di stampare un errore e fermarsi, possiamo chiedere di nuovo.

Esempio:

while True:
    try:
        age = int(input("Age: "))
        break
    except ValueError:
        print("Please enter a valid number.")

print(f"You are {age} years old.")

Come funziona:

Inizia un ciclo infinito.
Chiede l’età.
Prova a convertirla.
Se funziona, esce dal ciclo.
Se fallisce, mostra un messaggio e chiede di nuovo.

Esempio:

Age: banana
Please enter a valid number.
Age: hello
Please enter a valid number.
Age: 25
You are 25 years old.

Questo è molto meglio.

Il programma non muore.

Educa l’utente.

Gentilmente.

Come un insegnante paziente.

Ma dentro probabilmente è stanco.

Funzione helper per input intero sicuro

Possiamo mettere questa logica in una funzione.

Crea un file:

safe_input.py

Scrivi:

def get_integer(prompt):
    while True:
        try:
            number = int(input(prompt))
            return number
        except ValueError:
            print("Please enter a valid number.")

age = get_integer("Age: ")

print(f"You are {age} years old.")

Ora get_integer() continua a chiedere finché l’utente inserisce un intero valido.

Questo è molto utile.

Puoi riutilizzarla molte volte.

Esempio:

quantity = get_integer("Quantity: ")
year = get_integer("Year: ")
score = get_integer("Score: ")

Funzioni più gestione degli errori.

Molto bene.

Molto pulito.

Molto “stiamo diventando seri adesso”.

Gestire input float

A volte hai bisogno di numeri decimali.

Esempio:

price = float(input("Price: "))

Anche questo può generare ValueError.

Funzione più sicura:

def get_float(prompt):
    while True:
        try:
            number = float(input(prompt))
            return number
        except ValueError:
            print("Please enter a valid number.")

price = get_float("Price: ")

print(f"Price: {price:.2f}")

Se l’utente inserisce:

abc

Il programma chiede di nuovo.

Se l’utente inserisce:

19.99

Funziona.

Questo è utile per:

prezzi
misure
medie
sconti
percentuali

In pratica, tutto ciò dove i decimali entrano nella stanza con scarpe serie.

Gestire ZeroDivisionError

La divisione per zero non è permessa.

Esempio:

result = 10 / 0

Python dà:

ZeroDivisionError

Esempio più sicuro:

try:
    number = float(input("Number: "))
    result = 100 / number
    print(f"Result: {result}")
except ZeroDivisionError:
    print("You cannot divide by zero.")
except ValueError:
    print("Please enter a valid number.")

Se l’utente inserisce:

0

Output:

You cannot divide by zero.

Se l’utente inserisce:

banana

Output:

Please enter a valid number.

Questo gestisce due possibili problemi.

Molto pratico.

Molto utile.

Molto “il programma ne ha viste di cose”.

Più blocchi except

Puoi gestire errori diversi separatamente.

Esempio:

try:
    number = int(input("Enter a number: "))
    result = 100 / number
    print(result)
except ValueError:
    print("That was not a valid number.")
except ZeroDivisionError:
    print("You cannot divide by zero.")

Questo è buono perché errori diversi hanno bisogno di messaggi diversi.

ValueError significa:

L’input non era un numero.

ZeroDivisionError significa:

Il numero era zero.

Problema diverso.

Risposta diversa.

I buoni programmi spiegano i problemi chiaramente.

I cattivi programmi dicono:

Something went wrong.

E poi spariscono nella nebbia.

Non essere nebbia.

Gestire FileNotFoundError

Hai già visto i file.

Leggere un file mancante causa FileNotFoundError.

Esempio:

try:
    with open("notes.txt", "r") as file:
        content = file.read()

    print(content)
except FileNotFoundError:
    print("The file does not exist yet.")

Se notes.txt esiste, il programma lo legge.

Se non esiste, il programma stampa:

The file does not exist yet.

Questo è molto meglio che crashare.

Gli errori con i file sono comuni.

Specialmente quando:

il file non è mai stato creato
il nome del file è sbagliato
il file è in un’altra cartella
stai eseguendo Python dalla directory sbagliata

Classico dramma dei file.

Molto normale.

Molto risolvibile.

Caricare task in modo sicuro

Nella lezione precedente abbiamo salvato task in un file.

Ora possiamo caricarli in modo sicuro.

Esempio:

def load_tasks():
    tasks = []

    try:
        with open("tasks.txt", "r") as file:
            for line in file:
                tasks.append(line.strip())
    except FileNotFoundError:
        pass

    return tasks

Se il file esiste, i task vengono caricati.

Se il file non esiste, la funzione restituisce una lista vuota.

Questo è buono.

La prima volta che il programma gira, potrebbe non esserci ancora un file.

Non è un disastro.

È solo un programma nuovo senza dati.

Molto innocente.

Molto vuoto.

Molto pronto a diventare disordinato più tardi.

Usare else con try

Python supporta anche else con try.

Il blocco else viene eseguito solo se non succede nessun errore.

Esempio:

try:
    number = int(input("Enter a number: "))
except ValueError:
    print("Invalid number.")
else:
    print(f"Good number: {number}")

Se l’input è valido:

Enter a number: 10
Good number: 10

Se l’input non è valido:

Enter a number: banana
Invalid number.

Il blocco else è opzionale.

Non ti serve sempre.

Ma a volte rende il codice più chiaro.

Idea semplice:

prova cosa rischiosa
except se fallisce
else se riesce

Molto ordinato.

Molto Python.

Come un piccolo contratto legale per gli errori.

Usare finally

Python ha anche finally.

Il blocco finally viene eseguito sempre.

Esempio:

try:
    number = int(input("Enter a number: "))
    print(number)
except ValueError:
    print("Invalid number.")
finally:
    print("Program finished.")

Se l’utente inserisce un numero, finally viene eseguito.

Se l’utente inserisce input sbagliato, finally viene comunque eseguito.

Esempio:

Enter a number: banana
Invalid number.
Program finished.

finally è utile quando devi pulire qualcosa.

Per programmi da principianti non ti servirà spesso.

Ma è bene sapere che esiste.

Come un estintore.

Non lo usi ogni giorno.

Ma vuoi averlo lì.

Evita di catturare tutto troppo presto

Potresti vedere codice così:

try:
    number = int(input("Number: "))
except:
    print("Something went wrong.")

Questo cattura ogni errore.

Funziona.

Ma di solito non è ideale.

Perché?

Perché può nascondere bug reali.

Meglio:

try:
    number = int(input("Number: "))
except ValueError:
    print("Please enter a valid number.")

Questo gestisce il problema specifico che ti aspetti.

La gestione specifica degli errori è migliore.

Ti dice cosa è davvero successo.

Catturare tutto è come mettere una coperta sopra il problema.

Il problema è ancora lì.

Ora è solo più caldo.

E più difficile da vedere.

Non mettere troppo codice in try

Male:

try:
    name = input("Name: ")
    age = int(input("Age: "))
    city = input("City: ")
    print(user_name)
except ValueError:
    print("Invalid age.")

Questo è rischioso perché il blocco try contiene troppo.

Potrebbero esserci altri errori dentro.

Per esempio:

print(user_name)

potrebbe causare NameError.

Meglio:

name = input("Name: ")

try:
    age = int(input("Age: "))
except ValueError:
    print("Invalid age.")

city = input("City: ")

Tieni try concentrato sul codice che può produrre l’errore previsto.

Questo rende il debugging più facile.

E un debugging più facile è felicità.

Piccola felicità.

Ma sempre felicità.

Error handling non serve a nascondere codice rotto

Lezione importante.

La gestione degli errori non è un cestino per logica rotta.

Cattiva idea:

try:
    broken_code_here
except:
    pass

Questo nasconde i problemi.

Il programma può continuare, ma qualcosa è sbagliato.

E ora non sai cosa.

except: pass può essere utile in casi rari.

Ma i principianti dovrebbero evitare di usarlo con leggerezza.

Se ignori ogni errore, il tuo programma diventa una creatura misteriosa.

Gira.

Forse.

Fa cose.

Forse.

Nessuno sa perché.

Questa non è ingegneria.

È programmazione infestata.

Evita.

Mini programma: input età sicuro

Crea un file:

safe_age.py

Scrivi:

while True:
    try:
        age = int(input("Age: "))
        break
    except ValueError:
        print("Please enter a valid age.")

print(f"Your age is {age}.")

Esempio:

Age: hello
Please enter a valid age.
Age: 33
Your age is 33.

Questo programma continua a chiedere finché l’utente inserisce un intero valido.

Semplice.

Utile.

Molto meglio che crashare.

Questo è un pattern molto comune per principianti.

Usalo spesso.

I tuoi utenti scriveranno cose strane.

Il tuo programma non deve svenire.

Mini programma: calcolatrice sicura

Crea un file:

safe_calculator.py

Scrivi:

try:
    first_number = float(input("First number: "))
    second_number = float(input("Second number: "))

    result = first_number / second_number

    print(f"Result: {result:.2f}")
except ValueError:
    print("Please enter valid numbers.")
except ZeroDivisionError:
    print("You cannot divide by zero.")

Esempio:

First number: 10
Second number: 2
Result: 5.00

Se l’utente inserisce:

Second number: 0

Output:

You cannot divide by zero.

Questo programma gestisce due problemi comuni.

Input non valido.

Divisione per zero.

Molto utile.

Molto pratico.

Molto “la calcolatrice ora ha istinti di sopravvivenza”.

Mini programma: lettore note sicuro

Crea un file:

safe_notes_reader.py

Scrivi:

try:
    with open("notes.txt", "r") as file:
        print("Notes:")

        for line in file:
            print(f"- {line.strip()}")
except FileNotFoundError:
    print("No notes found yet.")

Se il file esiste, mostra le note.

Se non esiste, stampa:

No notes found yet.

Questo è pulito.

Un file mancante non è sempre un disastro.

A volte significa solo:

Ancora nessun dato.

È una situazione normale.

I buoni programmi capiscono le situazioni normali.

I cattivi programmi vanno nel panico e lanciano testo rosso.

Preferiamo programmi calmi.

Molto Zen.

Molto Python.

Mini programma: task manager più sicuro

Ora miglioriamo il task manager.

Crea un file:

safe_task_manager.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 show_menu():
    print("----- Task Manager -----")
    print("1. Add task")
    print("2. Show tasks")
    print("3. Remove task")
    print("q. Quit")

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):
    if len(tasks) == 0:
        print("No tasks to remove.")
        return

    show_tasks(tasks)

    try:
        task_number = int(input("Task number to remove: "))
        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}")
    except ValueError:
        print("Please enter a valid number.")

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":
        remove_task(tasks)
    elif choice == "q":
        print("Goodbye.")
        break
    else:
        print("Unknown option.")

Questo è un programma più forte.

Usa:

Questo è software reale per principianti.

Piccolo, sì.

Ma reale.

Salva dati.

Carica dati.

Gestisce file mancanti.

Gestisce input sbagliato.

Così crescono i programmi.

Una rete di sicurezza alla volta.

Cosa fa enumerate()

Nel task manager abbiamo usato:

for index, task in enumerate(tasks, start=1):
    print(f"{index}. {task}")

enumerate() dà sia:

numero
elemento

Esempio:

tasks = ["Buy milk", "Study Python"]

for index, task in enumerate(tasks, start=1):
    print(f"{index}. {task}")

Output:

1. Buy milk
2. Study Python

Questo è utile quando mostri liste numerate.

L’utente vede task numero 1.

L’indice reale della lista Python è 0.

Quindi quando rimuoviamo un task, facciamo:

index = task_number - 1

Numero amico dell’utente.

Indice amico di Python.

Diplomazia.

Tra umani e macchine.

Molto importante.

Errore comune: gestire l’errore sbagliato

Esempio:

try:
    age = int(input("Age: "))
except FileNotFoundError:
    print("File not found.")

Questo non ha senso.

Il codice rischioso converte input in int.

L’errore probabile è:

ValueError

Corretto:

try:
    age = int(input("Age: "))
except ValueError:
    print("Invalid age.")

Gestisci l’errore che può davvero succedere.

Altrimenti il programma sorveglia la porta sbagliata.

E il bug entra dalla finestra.

Classico comportamento da bug.

Errore comune: except troppo generico

Non ideale:

try:
    age = int(input("Age: "))
except:
    print("Something went wrong.")

Meglio:

try:
    age = int(input("Age: "))
except ValueError:
    print("Please enter a valid age.")

Specifico è meglio.

Più specifica è la tua gestione degli errori, più facile sarà il debugging.

Un messaggio vago non aiuta nessuno.

Specialmente te.

E sei tu che devi sistemare il codice.

Sii gentile con il tuo io futuro.

Il tuo io futuro ha già sofferto abbastanza.

Errore comune: ignorare completamente l’errore

Pericoloso:

try:
    age = int(input("Age: "))
except ValueError:
    pass

Se l’utente inserisce input non valido, non succede nulla.

Il programma ignora silenziosamente il problema.

Questo può creare bug confusi.

Meglio:

try:
    age = int(input("Age: "))
except ValueError:
    print("Please enter a valid age.")

Almeno dì all’utente cosa è successo.

Il fallimento silenzioso è pericoloso.

È come un allarme antincendio che sussurra.

Non utile.

Errore comune: usare una variabile che non è stata creata

Esempio:

try:
    age = int(input("Age: "))
except ValueError:
    print("Invalid age.")

print(age)

Se l’utente inserisce input non valido, age non viene mai creata.

Poi:

print(age)

può causare:

NameError

Meglio:

try:
    age = int(input("Age: "))
    print(age)
except ValueError:
    print("Invalid age.")

Oppure usa un ciclo finché ricevi input valido.

Molto importante.

Se una variabile viene creata dentro un blocco try, assicurati che esista prima di usarla dopo.

Python non è magia.

Non inventerà la variabile per pietà.

Errore comune: error handling senza sistemare il flusso

Male:

try:
    number = int(input("Number: "))
except ValueError:
    print("Invalid number.")

result = number * 2
print(result)

Se l’input non è valido, number potrebbe non esistere.

Meglio:

while True:
    try:
        number = int(input("Number: "))
        break
    except ValueError:
        print("Invalid number.")

result = number * 2
print(result)

Ora il programma continua solo quando number è valido.

Questo è un buon flusso.

La gestione degli errori non serve solo a stampare messaggi.

Serve a controllare cosa succede dopo.

Molto importante.

Molto architettura del programma.

Piccola architettura.

Ma sempre architettura.

Pratica

Crea un file:

practice_error_handling.py

Scrivi un programma che:

Soluzione esempio:

def get_float(prompt):
    while True:
        try:
            value = float(input(prompt))
            return value
        except ValueError:
            print("Please enter a valid number.")

def get_integer(prompt):
    while True:
        try:
            value = int(input(prompt))
            return value
        except ValueError:
            print("Please enter a valid whole number.")

price = get_float("Price: ")
quantity = get_integer("Quantity: ")

total = price * quantity

print(f"Total: {total:.2f}")

Esempio:

Price: hello
Please enter a valid number.
Price: 10.50
Quantity: banana
Please enter a valid whole number.
Quantity: 3
Total: 31.50

Questo è un programma pulito.

Gestisce input sbagliato.

Riutilizza funzioni.

Calcola in modo sicuro.

Ottima pratica.

Molto simile a un negozio.

Le tasse sono ancora nascoste lì vicino.

Ma non oggi.

Mini sfida

Crea un file:

safe_contact_book.py

Costruisci una piccola rubrica che:

Formato contatto semplice:

name,email,phone

Struttura esempio:

FILE_NAME = "contacts.txt"

def load_contacts():
    contacts = []

    try:
        with open(FILE_NAME, "r") as file:
            for line in file:
                parts = line.strip().split(",")

                if len(parts) == 3:
                    contact = {
                        "name": parts[0],
                        "email": parts[1],
                        "phone": parts[2]
                    }

                    contacts.append(contact)
    except FileNotFoundError:
        pass

    return contacts

def save_contacts(contacts):
    with open(FILE_NAME, "w") as file:
        for contact in contacts:
            file.write(f"{contact['name']},{contact['email']},{contact['phone']}\n")

def add_contact(contacts):
    contact = {
        "name": input("Name: "),
        "email": input("Email: "),
        "phone": input("Phone: ")
    }

    contacts.append(contact)
    save_contacts(contacts)

    print("Contact saved.")

def show_contacts(contacts):
    if len(contacts) == 0:
        print("No contacts yet.")
    else:
        print("Contacts:")

        for contact in contacts:
            print("-----")
            print(f"Name: {contact['name']}")
            print(f"Email: {contact['email']}")
            print(f"Phone: {contact['phone']}")

def show_menu():
    print("----- Contact Book -----")
    print("1. Add contact")
    print("2. Show contacts")
    print("q. Quit")

contacts = load_contacts()

while True:
    show_menu()

    choice = input("Choose an option: ").lower()

    if choice == "1":
        add_contact(contacts)
    elif choice == "2":
        show_contacts(contacts)
    elif choice == "q":
        print("Goodbye.")
        break
    else:
        print("Unknown option.")

Questo programma combina molte cose che hai imparato:

È tanto.

Non è più solo “Hello, World!”.

È un piccolo programma reale.

Salva dati.

Carica dati.

Usa informazioni strutturate.

Gestisce file mancanti.

Progresso molto forte.

Molto Python.

Checklist per principianti

Quando la tua gestione degli errori non funziona, controlla:

Ho messo il codice rischioso dentro try?
Sto catturando l’errore corretto?
Ho usato ValueError per conversioni numeriche non valide?
Ho usato FileNotFoundError per file mancanti?
Ho usato ZeroDivisionError per divisione per zero?
Ho catturato tutto per sbaglio con except generico?
Sto nascondendo errori con pass?
Il programma continua in modo sicuro dopo un errore?
Una variabile potrebbe mancare dopo un try fallito?
Dovrei usare un ciclo per chiedere di nuovo?
Il mio blocco try è troppo grande?

La gestione degli errori non serve a far sparire gli errori.

Serve a rispondere correttamente.

Un buon programma non finge che i problemi non esistano.

Un buon programma dice:

Mi aspettavo questo problema.
Ecco cosa facciamo.

Molto calmo.

Molto adulto.

Molto utile.

Riassunto

Oggi hai imparato:

Questo è un grande passo.

I tuoi programmi ora sono più sicuri.

Possono gestire input sbagliato.

Possono gestire file mancanti.

Possono evitare crash teatrali.

Possono guidare l’utente invece di esplodere.

Questo serve ai programmi reali.

Perché la vita reale è disordinata.

I file spariscono.

Gli utenti scrivono banana.

I numeri diventano zero.

E a volte dimentichi cosa hai scritto ieri.

La gestione degli errori aiuta il tuo programma a sopravvivere al caos.

Molto utile.

Molto Python.

Molto reale.

Prossima lezione

Nella prossima lezione impareremo a lavorare con moduli e import.

Imparerai a dividere il codice in file diversi e riutilizzare codice nel tuo progetto.

Invece di tenere tutto in un unico file gigante, organizzerai il codice così:

main.py
helpers.py
calculator.py
tasks.py

Poi importerai funzioni da un file all’altro.

Questo è un altro enorme passo verso progetti reali.

Perché i progetti reali non sono un unico file gigante.

Di solito.

A meno che qualcuno abbia sofferto.

E noi non vogliamo questo.

Vogliamo struttura pulita.

Codice riutilizzabile.

E meno file chiamati final_final_really_final.py.

Molto Python.

Molto livello successivo.