Esercizi di Python

In questa pagina trovi 10 esercizi di Python ordinati per difficoltà: si parte da print e input e si arriva a dizionari, file JSON e programmazione a oggetti.

Ogni esercizio contiene la consegna, un richiamo di teoria, la soluzione e la spiegazione dettagliata. Per eseguire il codice ti basta Python 3 installato (python3 esercizio.py) oppure un ambiente online.

Indice degli esercizi


Esercizio 1 – Il primo programma: print e input

Livello: Base · Argomenti: print, input, stringhe

Consegna: Chiedi all’utente il suo nome e salutalo con un messaggio personalizzato, ad esempio «Ciao Marco, benvenuto in Python!».

Teoria in breve: print() mostra un testo a schermo, input() legge ciò che l’utente scrive e lo restituisce sempre come stringa. In Python non serve dichiarare il tipo delle variabili: basta assegnare un valore.

Soluzione

nome = input("Come ti chiami? ")
print(f"Ciao {nome}, benvenuto in Python!")

Spiegazione

La variabile nome riceve il testo digitato dall’utente.

La f davanti alle virgolette crea una f-string: tutto ciò che è tra parentesi graffe { } viene sostituito con il valore della variabile. È il modo più moderno e leggibile di comporre testi in Python.


Esercizio 2 – Calcolatore dell’età in giorni

Livello: Base · Argomenti: int(), operazioni aritmetiche, conversione di tipo

Consegna: Chiedi l’età in anni e stampa circa quanti mesi, giorni e ore hai vissuto.

Teoria in breve: Poiché input() restituisce una stringa, per fare calcoli bisogna convertirla in numero con int() (interi) o float() (decimali). Gli operatori aritmetici sono + - * /, più // (divisione intera), % (resto) e ** (potenza).

Soluzione

anni = int(input("Quanti anni hai? "))

mesi = anni * 12
giorni = anni * 365
ore = giorni * 24

print(f"Hai vissuto circa {mesi} mesi,")
print(f"{giorni:,} giorni e {ore:,} ore!")

Spiegazione

int(input(...)) legge il testo e lo trasforma subito in numero intero. Se l’utente scrivesse una parola, Python solleverebbe un errore ValueError (lo gestiremo nell’esercizio 10).

Nella f-string {giorni:,} il formato :, aggiunge il separatore delle migliaia, rendendo i numeri grandi più leggibili.


Esercizio 3 – Dal voto al giudizio

Livello: Base · Argomenti: if / elif / else, operatori di confronto

Consegna: Leggi un voto da 1 a 10 e stampa il giudizio: insufficiente (< 6), sufficiente (6), buono (7–8), ottimo (9–10). Se il voto non è valido, segnalalo.

Teoria in breve: Le condizioni si scrivono con if, elif (else if) ed else. In Python i blocchi sono delimitati dall’indentazione (4 spazi), non da parentesi graffe. Python permette anche confronti concatenati come 7 <= voto <= 8.

Soluzione

voto = int(input("Inserisci il voto (1-10): "))

if voto < 1 or voto > 10:
    print("Voto non valido")
elif voto < 6:
    print("Insufficiente")
elif voto == 6:
    print("Sufficiente")
elif 7 <= voto <= 8:
    print("Buono")
else:
    print("Ottimo")

Spiegazione

Prima controlliamo che il voto sia valido, così gli altri rami lavorano solo su valori corretti. or è l’operatore logico «oppure».

Le condizioni sono valutate dall’alto verso il basso e viene eseguito solo il primo ramo vero. Per questo all’else finale arrivano solo i voti 9 e 10.


Esercizio 4 – Somma dei numeri e tabellina con for

Livello: Base · Argomenti: ciclo for, range()

Consegna: Leggi un numero N, stampa la somma di tutti i numeri da 1 a N e poi la tabellina di N.

Teoria in breve: Il ciclo for in Python scorre gli elementi di una sequenza. range(a, b) genera i numeri da a a b-1: l’estremo finale è escluso.

Soluzione

n = int(input("Inserisci N: "))

somma = 0
for i in range(1, n + 1):
    somma += i
print(f"La somma da 1 a {n} è {somma}")

print(f"Tabellina del {n}:")
for i in range(1, 11):
    print(f"{n} x {i} = {n * i}")

Spiegazione

Usiamo range(1, n + 1) perché vogliamo includere anche N. somma += i è l’abbreviazione di somma = somma + i: è il classico schema dell’accumulatore.

Curiosità: la stessa somma si ottiene con sum(range(1, n + 1)) oppure con la formula di Gauss n * (n + 1) // 2.


Esercizio 5 – Indovina il numero

Livello: Intermedio · Argomenti: ciclo while, modulo random, break

Consegna: Il programma sceglie un numero casuale tra 1 e 100 e l’utente deve indovinarlo con l’aiuto dei suggerimenti «troppo alto / troppo basso». Alla fine mostra i tentativi usati.

Teoria in breve: Il ciclo while ripete un blocco finché una condizione è vera; con while True si crea un ciclo infinito da cui si esce con break. I moduli estendono Python: random serve a generare numeri casuali.

Soluzione

import random

segreto = random.randint(1, 100)
tentativi = 0
print("Ho pensato un numero tra 1 e 100!")

while True:
    tentativo = int(input("Il tuo tentativo: "))
    tentativi += 1
    if tentativo < segreto:
        print("Troppo basso!")
    elif tentativo > segreto:
        print("Troppo alto!")
    else:
        print(f"Indovinato in {tentativi} tentativi!")
        break

Spiegazione

import random carica il modulo; random.randint(1, 100) restituisce un intero tra 1 e 100, estremi inclusi.

Il ciclo while True continua all’infinito: quando l’utente indovina stampiamo il risultato ed usciamo con break.


Esercizio 6 – Statistiche su una lista

Livello: Intermedio · Argomenti: liste, funzioni built-in, split, list comprehension

Consegna: L’utente scrive una serie di numeri separati da spazi. Stampa quanti sono, il massimo, il minimo, la media, la lista ordinata e solo i numeri pari.

Teoria in breve: Una lista è una collezione ordinata e modificabile, scritta tra parentesi quadre. Python offre funzioni pronte come len, max, min, sum e sorted. Le list comprehension creano una nuova lista in una sola riga.

Soluzione

testo = input("Scrivi dei numeri separati da spazi: ")
numeri = [int(x) for x in testo.split()]

print("Quantità:", len(numeri))
print("Massimo:", max(numeri))
print("Minimo:", min(numeri))
print(f"Media: {sum(numeri) / len(numeri):.2f}")
print("Ordinati:", sorted(numeri))

pari = [n for n in numeri if n % 2 == 0]
print("Numeri pari:", pari)

Spiegazione

testo.split() divide la stringa negli spazi e restituisce una lista di stringhe, ad es. ['4', '9', '2'].

[int(x) for x in ...] è una list comprehension: per ogni elemento x calcola int(x) e raccoglie i risultati in una nuova lista. Con if alla fine possiamo anche filtrare, come facciamo per i numeri pari.

sorted() restituisce una nuova lista ordinata senza modificare l’originale (al contrario del metodo numeri.sort()).


Esercizio 7 – Numeri primi con le funzioni

Livello: Intermedio · Argomenti: def, return, parametri, ottimizzazione

Consegna: Scrivi una funzione e_primo(n) che restituisca True se n è primo, e una funzione primi_fino_a(limite) che restituisca la lista dei numeri primi fino a quel limite.

Teoria in breve: Le funzioni si definiscono con def, ricevono parametri e restituiscono un valore con return. Spezzare un problema in piccole funzioni rende il codice più chiaro, testabile e riutilizzabile.

Soluzione

def e_primo(n):
    """Restituisce True se n è un numero primo."""
    if n < 2:
        return False
    for d in range(2, int(n ** 0.5) + 1):
        if n % d == 0:
            return False
    return True


def primi_fino_a(limite):
    return [n for n in range(2, limite + 1) if e_primo(n)]


limite = int(input("Trova i numeri primi fino a: "))
print(primi_fino_a(limite))

Spiegazione

Un numero è primo se è divisibile solo per 1 e per sé stesso. Basta controllare i divisori fino alla radice quadrata di n (n ** 0.5): se n avesse un divisore più grande, ne avrebbe anche uno più piccolo già controllato. Questo rende la funzione molto più veloce.

Appena troviamo un divisore usciamo subito con return False. La stringa tra triple virgolette è una docstring: documenta cosa fa la funzione.

primi_fino_a riutilizza e_primo dentro una list comprehension: ecco il vantaggio di scrivere funzioni piccole e componibili.


Esercizio 8 – Contare le parole di un testo

Livello: Avanzato · Argomenti: stringhe, dizionari, ordinamento con key

Consegna: Leggi un testo e stampa le 5 parole più frequenti con il numero di occorrenze, ignorando maiuscole e punteggiatura.

Teoria in breve: Un dizionario (dict) associa delle chiavi a dei valori, come una rubrica: {'ciao': 3}. È perfetto per contare le occorrenze. I metodi delle stringhe (lower, strip, split) aiutano a «pulire» il testo.

Soluzione

import string

testo = input("Incolla un testo: ")

conteggio = {}
for parola in testo.lower().split():
    parola = parola.strip(string.punctuation)
    if parola:
        conteggio[parola] = conteggio.get(parola, 0) + 1

classifica = sorted(conteggio.items(), key=lambda coppia: coppia[1], reverse=True)

print("Le 5 parole più frequenti:")
for parola, volte in classifica[:5]:
    print(f"{parola}: {volte}")

Spiegazione

testo.lower().split() porta tutto in minuscolo e divide in parole; strip(string.punctuation) elimina la punteggiatura all’inizio e alla fine di ogni parola.

conteggio.get(parola, 0) restituisce il valore attuale o 0 se la parola non c’è ancora: così non serve controllare a parte se la chiave esiste.

items() fornisce le coppie (parola, conteggio). sorted con key=lambda coppia: coppia[1] ordina in base al conteggio, reverse=True in ordine decrescente; [:5] prende i primi cinque elementi (slicing).

Nella libreria standard esiste anche collections.Counter, che fa tutto questo in una riga: Counter(parole).most_common(5).


Esercizio 9 – Rubrica salvata su file

Livello: Avanzato · Argomenti: file, JSON, with, menu interattivo

Consegna: Crea una piccola rubrica a menu che permetta di aggiungere un contatto, cercarlo, elencarli tutti e uscire. I contatti devono essere salvati in un file rubrica.json così da non perderli alla chiusura.

Teoria in breve: Per rendere i dati persistenti bisogna salvarli su file. Il blocco with open(...) apre il file e lo chiude automaticamente. Il modulo json converte dizionari e liste Python in testo e viceversa.

Soluzione

import json
import os

FILE = "rubrica.json"


def carica():
    if os.path.exists(FILE):
        with open(FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    return {}


def salva(rubrica):
    with open(FILE, "w", encoding="utf-8") as f:
        json.dump(rubrica, f, indent=2, ensure_ascii=False)


rubrica = carica()
while True:
    scelta = input("\n1) Aggiungi  2) Cerca  3) Elenca  4) Esci\nScelta: ")
    if scelta == "1":
        nome = input("Nome: ").strip()
        telefono = input("Telefono: ").strip()
        rubrica[nome] = telefono
        salva(rubrica)
        print("Contatto salvato.")
    elif scelta == "2":
        nome = input("Nome da cercare: ").strip()
        print(rubrica.get(nome, "Contatto non trovato."))
    elif scelta == "3":
        for nome, telefono in sorted(rubrica.items()):
            print(f"{nome}: {telefono}")
    elif scelta == "4":
        print("Arrivederci!")
        break
    else:
        print("Scelta non valida.")

Spiegazione

carica() controlla con os.path.exists se il file esiste: se sì lo legge con json.load, altrimenti parte da un dizionario vuoto.

salva() apre il file in scrittura ("w") e lo riscrive con json.dump; indent=2 lo rende leggibile e ensure_ascii=False conserva le lettere accentate.

Il ciclo while True mostra il menu finché l’utente non sceglie 4. Ogni modifica viene salvata subito, così i dati restano anche dopo aver chiuso il programma.


Esercizio 10 – Conto bancario con le classi

Livello: Avanzato · Argomenti: classi, oggetti, metodi, eccezioni, ereditarietà

Consegna: Crea una classe ContoBancario con titolare e saldo, e i metodi deposita, preleva e la stampa del conto. Impedisci importi negativi e prelievi superiori al saldo sollevando eccezioni. Crea poi una sottoclasse ContoRisparmio che aggiunga gli interessi.

Teoria in breve: La programmazione a oggetti (OOP) raggruppa dati (attributi) e comportamenti (metodi) in una classe, il «progetto» da cui si creano gli oggetti. Le eccezioni segnalano situazioni di errore e si gestiscono con try / except. L’ereditarietà permette di creare una classe a partire da un’altra.

Soluzione

class ContoBancario:
    def __init__(self, titolare, saldo=0.0):
        self.titolare = titolare
        self.saldo = saldo

    def deposita(self, importo):
        if importo <= 0:
            raise ValueError("L'importo deve essere positivo")
        self.saldo += importo

    def preleva(self, importo):
        if importo <= 0:
            raise ValueError("L'importo deve essere positivo")
        if importo > self.saldo:
            raise ValueError("Saldo insufficiente")
        self.saldo -= importo

    def __str__(self):
        return f"Conto di {self.titolare}: {self.saldo:.2f} €"


class ContoRisparmio(ContoBancario):
    def __init__(self, titolare, saldo=0.0, tasso=0.02):
        super().__init__(titolare, saldo)
        self.tasso = tasso

    def applica_interessi(self):
        self.deposita(self.saldo * self.tasso)


conto = ContoRisparmio("Giulia", 1000)
conto.deposita(500)
conto.applica_interessi()
print(conto)

try:
    conto.preleva(5000)
except ValueError as errore:
    print("Operazione rifiutata:", errore)

Spiegazione

__init__ è il costruttore: viene eseguito quando si crea un oggetto e inizializza gli attributi. self rappresenta l’oggetto stesso.

deposita e preleva controllano i dati e, se qualcosa non va, usano raise per sollevare un’eccezione invece di lasciare il conto in uno stato sbagliato. __str__ definisce come l’oggetto viene stampato con print.

ContoRisparmio(ContoBancario) eredita tutti i metodi del conto base; super().__init__ richiama il costruttore del genitore e poi aggiungiamo il tasso. Il nuovo metodo applica_interessi riutilizza deposita.

Il blocco try / except intercetta l’errore del prelievo troppo alto e mostra un messaggio chiaro invece di far terminare il programma.


Consiglio: prova sempre a risolvere l’esercizio da solo prima di leggere la soluzione, poi modificalo per sperimentare: è il modo più veloce per imparare.