Contenuti




Python: Guida Completa alla Programmazione dal Principiante all'Esperto

Dal principiante assoluto al developer professionista: tutto quello che devi sapere su Python


Python: Guida Completa alla Programmazione dal Principiante all’Esperto

Benvenuto nella guida più completa di Python disponibile online! Python è uno dei linguaggi di programmazione più versatili e richiesti al mondo, utilizzato in web development, data science, intelligenza artificiale, automazione e molto altro.

Cosa troverai in questa guida completa
  • Fondamenti solidi: Sintassi, tipi di dati, strutture di controllo
  • Programmazione avanzata: OOP, decoratori, context manager, metaclassi
  • Scienza dei dati: NumPy, Pandas, Matplotlib, analisi dati
  • Sviluppo web: Flask, Django, API REST
  • Apprendimento automatico: scikit-learn, introduzione al ML
  • Buone pratiche: PEP 8, testing, debugging, deployment
  • Progetti pratici: Applicazioni reali per consolidare le competenze

Indice della Guida Completa

Parte I: Fondamenti di Python

  1. Introduzione e configurazione
  2. Sintassi di Base e Tipi di Dati
  3. Strutture di Controllo
  4. Funzioni e Scope
  5. Strutture Dati Avanzate

Parte II: Programmazione Intermedia

  1. Programmazione Orientata agli Oggetti
  2. Gestione degli Errori
  3. Moduli e Package
  4. File I/O e Serializzazione
  5. Decoratori e Context Manager

Parte III: Programmazione Avanzata

  1. Programmazione Funzionale
  2. Multithreading e Asyncio
  3. Metaclassi e Descriptors
  4. Testing e Debugging
  5. Performance e Ottimizzazione

Parte IV: Scienza dei dati e apprendimento automatico

  1. NumPy per Calcolo Scientifico
  2. Pandas per Data Analysis
  3. Visualizzazione con Matplotlib e Seaborn
  4. Apprendimento automatico con scikit-learn
  5. Introduzione a TensorFlow e PyTorch

Parte V: Sviluppo web

  1. Flask: Micro Web Framework
  2. Django: Full-Stack Framework
  3. API REST e FastAPI
  4. Database e ORM
  5. Deployment e Produzione

Parte VI: Progetti Pratici

  1. Progetto 1: Sistema di Gestione Biblioteca
  2. Progetto 2: Web Scraper Avanzato
  3. Progetto 3: Dashboard Analytics
  4. Progetto 4: Chatbot con NLP
  5. Progetto 5: API REST Completa

Parte I: Fondamenti di Python

Introduzione e configurazione

Perché Python?

I vantaggi di Python
  • Sintassi leggibile: Codice pulito e facile da comprendere
  • Versatilità: Web dev, data science, AI, automazione, gaming
  • Community attiva: Vasta libreria di pacchetti e supporto
  • Performance: Ottimo per prototipazione rapida e sviluppo
  • Opportunità lavorative: Uno dei linguaggi più richiesti

Installazione Python

Windows

1
2
3
4
5
6
7
# Scaricare da python.org o usare Microsoft Store
# Oppure con chocolatey
choco install python

# Verificare installazione
python --version
pip --version

macOS

1
2
3
4
5
6
# Con Homebrew (consigliato)
brew install python

# Verificare installazione
python3 --version
pip3 --version

Linux (Ubuntu/Debian)

1
2
3
4
5
6
7
# Python è solitamente pre-installato
sudo apt update
sudo apt install python3 python3-pip python3-venv

# Verificare installazione
python3 --version
pip3 --version

Configurazione ambiente di sviluppo

Virtual Environment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Creare virtual environment
python -m venv myproject_env

# Attivazione
# Windows
myproject_env\Scripts\activate
# macOS/Linux
source myproject_env/bin/activate

# Installare pacchetti
pip install requests numpy pandas matplotlib

# Salvare dipendenze
pip freeze > requirements.txt

# Installare da requirements
pip install -r requirements.txt

Per approfondire venv e Poetry, vedi guida ai virtual environment.

Editor e IDE Consigliati

  • VS Code: Leggero, con ottime estensioni Python
  • PyCharm: IDE completo per Python professional
  • Jupyter Notebook: Perfetto per data science
  • Sublime Text: Editor veloce e personalizzabile

Sintassi di Base e Tipi di Dati

Il Tuo Primo Programma Python

1
2
3
4
5
6
7
8
9
# hello_world.py
print("Ciao, mondo!")
print("Benvenuto nella guida completa di Python!")

# Commenti inline
name = "Marco"  # Variabile stringa
age = 25        # Variabile intero

print(f"Mi chiamo {name} e ho {age} anni")

Tipi di Dati Fondamentali

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Numeri
intero = 42
decimale = 3.14159
complesso = 3 + 4j

# Stringhe
nome = "Python"
descrizione = 'Linguaggio potente'
multiriga = """
Questo è un testo
su più righe
molto utile per documentazione
"""

# Boolean
vero = True
falso = False
risultato = 10 > 5  # True

# None (valore nullo)
vuoto = None

# Controllo tipo
print(type(intero))      # <class 'int'>
print(type(descrizione)) # <class 'str'>
print(isinstance(nome, str))  # True

Operazioni con Stringhe

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Concatenazione
nome = "Mario"
cognome = "Rossi"
nome_completo = nome + " " + cognome

# Formatting avanzato
eta = 30
messaggio = f"Mi chiamo {nome_completo} e ho {eta} anni"

# Metodi stringa utili
testo = "  Python è Fantastico!  "
print(testo.lower())        # python è fantastico!
print(testo.upper())        # PYTHON È FANTASTICO!
print(testo.strip())        # Python è Fantastico!
print(testo.replace("Python", "Java"))  # Java è Fantastico!

# Slicing
parola = "Python"
print(parola[0])      # P
print(parola[-1])     # n
print(parola[1:4])    # yth
print(parola[:3])     # Pyt
print(parola[3:])     # hon
print(parola[::-1])   # nohtyP (inverso)

Input e Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Input dall'utente
nome = input("Come ti chiami? ")
eta_str = input("Quanti anni hai? ")
eta = int(eta_str)  # Conversione a intero

# Output formattato
print(f"Ciao {nome}!")
print(f"Il prossimo anno avrai {eta + 1} anni")

# Formatting con format()
template = "Nome: {}, Età: {}, Città: {}"
print(template.format(nome, eta, "Roma"))

# Formatting con %
print("Nome: %s, Età: %d" % (nome, eta))

Strutture di Controllo

Condizioni (if/elif/else)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# Struttura condizionale base
eta = int(input("Inserisci la tua età: "))

if eta < 13:
    categoria = "bambino"
elif eta < 20:
    categoria = "adolescente"
elif eta < 65:
    categoria = "adulto"
else:
    categoria = "senior"

print(f"Sei un {categoria}")

# Operatori di confronto
a, b = 10, 20
print(a == b)   # False (uguale)
print(a != b)   # True (diverso)
print(a < b)    # True (minore)
print(a <= b)   # True (minore o uguale)
print(a > b)    # False (maggiore)
print(a >= b)   # False (maggiore o uguale)

# Operatori logici
x, y, z = True, False, True
print(x and y)  # False
print(x or y)   # True
print(not x)    # False
print(x and (y or z))  # True

# Condizioni complesse
voto = 85
if voto >= 90 and voto <= 100:
    print("Eccellente!")
elif 80 <= voto < 90:  # Confronto concatenato
    print("Molto bene!")
elif voto >= 60:
    print("Sufficiente")
else:
    print("Insufficiente")

Cicli (for/while)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Ciclo for con range
print("Conteggio da 1 a 5:")
for i in range(1, 6):
    print(f"Numero: {i}")

# Ciclo for con liste
frutti = ["mela", "banana", "arancia", "kiwi"]
for frutto in frutti:
    print(f"Mi piace la {frutto}")

# Enumerate per indice e valore
for indice, frutto in enumerate(frutti):
    print(f"{indice + 1}. {frutto}")

# Ciclo while
contatore = 0
while contatore < 5:
    print(f"Contatore: {contatore}")
    contatore += 1

# Cicli annidati
print("Tabella moltiplicazione:")
for i in range(1, 6):
    for j in range(1, 6):
        prodotto = i * j
        print(f"{i} x {j} = {prodotto}")
    print("-" * 20)

# Break e continue
numeri = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print("Numeri pari fino al primo dispari > 5:")
for num in numeri:
    if num % 2 != 0 and num > 5:
        break  # Esce dal ciclo
    if num % 2 != 0:
        continue  # Salta all'iterazione successiva
    print(num)

Funzioni e Scope

Definizione e Chiamata Funzioni

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# Funzione semplice
def saluta():
    print("Ciao! Benvenuto nella guida Python!")

saluta()  # Chiamata funzione

# Funzione con parametri
def saluta_persona(nome, cognome=""):
    if cognome:
        print(f"Ciao {nome} {cognome}!")
    else:
        print(f"Ciao {nome}!")

saluta_persona("Mario")           # Ciao Mario!
saluta_persona("Mario", "Rossi")  # Ciao Mario Rossi!

# Funzione con valore di ritorno
def calcola_area_cerchio(raggio):
    pi = 3.14159
    area = pi * raggio ** 2
    return area

area = calcola_area_cerchio(5)
print(f"Area del cerchio: {area}")

# Funzioni con multipli valori di ritorno
def calcola_statistiche(numeri):
    if not numeri:
        return None, None, None

    minimo = min(numeri)
    massimo = max(numeri)
    media = sum(numeri) / len(numeri)

    return minimo, massimo, media

dati = [1, 5, 3, 9, 2, 7, 4]
min_val, max_val, media_val = calcola_statistiche(dati)
print(f"Min: {min_val}, Max: {max_val}, Media: {media_val:.2f}")

Parametri Avanzati

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# Parametri con valori default
def crea_profilo(nome, eta, città="Non specificata", hobby="Nessuno"):
    return {
        "nome": nome,
        "eta": eta,
        "città": città,
        "hobby": hobby
    }

profilo1 = crea_profilo("Alice", 25)
profilo2 = crea_profilo("Bob", 30, "Milano", "Calcio")

# *args per parametri variabili posizionali
def somma_numeri(*numeri):
    return sum(numeri)

print(somma_numeri(1, 2, 3))           # 6
print(somma_numeri(10, 20, 30, 40))    # 100

# **kwargs per parametri variabili nominali
def crea_persona(**dettagli):
    persona = {}
    for chiave, valore in dettagli.items():
        persona[chiave] = valore
    return persona

p1 = crea_persona(nome="Carlo", eta=35, professione="Ingegnere")
p2 = crea_persona(nome="Sara", città="Roma", sport="Tennis")

# Combinazione di tutti i tipi di parametri
def funzione_complessa(nome, eta, *hobby, città="Roma", **altri_dettagli):
    print(f"Nome: {nome}")
    print(f"Età: {eta}")
    print(f"Città: {città}")
    print(f"Hobby: {', '.join(hobby)}")
    for chiave, valore in altri_dettagli.items():
        print(f"{chiave}: {valore}")

funzione_complessa("Marco", 28, "Lettura", "Cinema", "Viaggi",
                  città="Milano", professione="Developer",
                  linguaggio="Python")

Scope e Namespaces

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Variabili globali e locali
contatore_globale = 0  # Variabile globale

def incrementa_contatore():
    global contatore_globale  # Accesso alla variabile globale
    contatore_globale += 1
    return contatore_globale

def funzione_con_locale():
    contatore_locale = 10  # Variabile locale
    return contatore_locale

print(incrementa_contatore())  # 1
print(incrementa_contatore())  # 2
print(funzione_con_locale())   # 10
# print(contatore_locale)      # NameError!

# Nested functions e closure
def funzione_esterna(x):
    def funzione_interna(y):
        return x + y  # Accesso alla variabile della funzione esterna
    return funzione_interna

addiziona_5 = funzione_esterna(5)
print(addiziona_5(3))  # 8

# Lambda functions (funzioni anonime)
quadrato = lambda x: x ** 2
print(quadrato(4))  # 16

# Lambda con funzioni built-in
numeri = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pari = list(filter(lambda x: x % 2 == 0, numeri))
quadrati = list(map(lambda x: x ** 2, numeri))

print(f"Numeri pari: {pari}")      # [2, 4, 6, 8, 10]
print(f"Quadrati: {quadrati}")     # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Strutture Dati Avanzate

Liste Approfondite

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Creazione e inizializzazione liste
numeri = [1, 2, 3, 4, 5]
nomi = ["Alice", "Bob", "Charlie"]
mista = [1, "ciao", 3.14, True, None]
vuota = []

# List comprehension
quadrati = [x**2 for x in range(1, 11)]
print(quadrati)  # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

pari = [x for x in range(1, 21) if x % 2 == 0]
print(pari)  # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

# List comprehension annidate
matrice = [[i*j for j in range(1, 4)] for i in range(1, 4)]
print(matrice)  # [[1, 2, 3], [2, 4, 6], [3, 6, 9]]

# Metodi delle liste
frutti = ["mela", "banana", "arancia"]

# Aggiunta elementi
frutti.append("kiwi")           # Aggiunge alla fine
frutti.insert(1, "pera")        # Inserisce in posizione specifica
frutti.extend(["uva", "mango"]) # Aggiunge più elementi

# Rimozione elementi
frutti.remove("banana")         # Rimuove prima occorrenza
ultimo = frutti.pop()           # Rimuove e restituisce ultimo
secondo = frutti.pop(1)         # Rimuove e restituisce elemento in posizione

# Ricerca e conteggio
if "mela" in frutti:
    indice = frutti.index("mela")
    count = frutti.count("mela")

# Ordinamento
numeri = [3, 1, 4, 1, 5, 9, 2, 6]
numeri_ordinati = sorted(numeri)  # Non modifica originale
numeri.sort()                     # Modifica originale
numeri.reverse()                  # Inverte ordine

# Slicing avanzato
lista = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(lista[2:8:2])    # [2, 4, 6] (step 2)
print(lista[::3])      # [0, 3, 6, 9] (ogni 3)
print(lista[::-1])     # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] (inverso)

Dizionari Avanzati

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# Creazione dizionari
studente = {
    "nome": "Marco",
    "eta": 22,
    "percorso": "Informatica",
    "voti": [28, 30, 27, 29]
}

# Dictionary comprehension
quadrati_dict = {x: x**2 for x in range(1, 6)}
print(quadrati_dict)  # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# Operazioni sui dizionari
voti_materie = {"matematica": 28, "fisica": 30, "chimica": 27}

# Accesso e modifica
voto_matematica = voti_materie.get("matematica", 0)  # Sicuro
voti_materie["inglese"] = 29  # Aggiunta nuova chiave
voti_materie.update({"storia": 26, "geografia": 28})

# Metodi utili
chiavi = list(voti_materie.keys())
valori = list(voti_materie.values())
coppie = list(voti_materie.items())

# Iterazione
for materia, voto in voti_materie.items():
    print(f"{materia}: {voto}")

# Dizionari annidati
università = {
    "nome": "Università di Milano",
    "facoltà": {
        "informatica": {
            "studenti": 500,
            "professori": 25,
            "corsi": ["Algoritmi", "Database", "Reti"]
        },
        "matematica": {
            "studenti": 300,
            "professori": 20,
            "corsi": ["Analisi", "Algebra", "Geometria"]
        }
    }
}

# Accesso ai dati annidati
corsi_informatica = università["facoltà"]["informatica"]["corsi"]

Tuple e Set

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# Tuple (immutabili)
coordinate = (10, 20)
colori_rgb = (255, 128, 0)
dati_persona = ("Mario", "Rossi", 30, "Roma")

# Unpacking tuple
x, y = coordinate
nome, cognome, eta, città = dati_persona

# Tuple con un elemento (attenzione alla virgola!)
singolo = (5,)  # Tuple con un elemento
non_tuple = (5)  # Questo è solo un intero con parentesi

# Named tuples
from collections import namedtuple

Persona = namedtuple('Persona', ['nome', 'cognome', 'eta'])
p1 = Persona('Mario', 'Rossi', 30)
print(f"{p1.nome} {p1.cognome} ha {p1.eta} anni")

# Set (insiemi)
numeri_set = {1, 2, 3, 4, 5}
frutti_set = {"mela", "banana", "arancia"}

# Operazioni sui set
set1 = {1, 2, 3, 4, 5}
set2 = {4, 5, 6, 7, 8}

unione = set1 | set2           # {1, 2, 3, 4, 5, 6, 7, 8}
intersezione = set1 & set2     # {4, 5}
differenza = set1 - set2       # {1, 2, 3}
diff_simmetrica = set1 ^ set2  # {1, 2, 3, 6, 7, 8}

# Set comprehension
quadrati_set = {x**2 for x in range(1, 6)}

# Rimozione duplicati da lista
lista_con_duplicati = [1, 2, 2, 3, 3, 3, 4, 4, 5]
lista_unica = list(set(lista_con_duplicati))

Parte II: Programmazione Intermedia

Programmazione Orientata agli Oggetti

Classi e Oggetti Base

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Persona:
    # Variabile di classe (condivisa da tutte le istanze)
    specie = "Homo sapiens"

    def __init__(self, nome, eta, email):
        # Variabili di istanza
        self.nome = nome
        self.eta = eta
        self.email = email
        self.hobby = []

    def presentati(self):
        return f"Ciao, sono {self.nome} e ho {self.eta} anni"

    def aggiungi_hobby(self, hobby):
        self.hobby.append(hobby)

    def __str__(self):
        return f"Persona(nome={self.nome}, eta={self.eta})"

    def __repr__(self):
        return f"Persona('{self.nome}', {self.eta}, '{self.email}')"

# Creazione oggetti
persona1 = Persona("Alice", 25, "alice@email.com")
persona2 = Persona("Bob", 30, "bob@email.com")

# Utilizzo metodi
print(persona1.presentati())
persona1.aggiungi_hobby("Lettura")
persona1.aggiungi_hobby("Cinema")

print(f"Hobby di {persona1.nome}: {', '.join(persona1.hobby)}")

Ereditarietà e Polimorfismo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# Classe base
class Veicolo:
    def __init__(self, marca, modello, anno):
        self.marca = marca
        self.modello = modello
        self.anno = anno
        self.velocità = 0

    def accelera(self, incremento):
        self.velocità += incremento
        return f"Accelerando... Velocità attuale: {self.velocità} km/h"

    def frena(self, decremento):
        self.velocità = max(0, self.velocità - decremento)
        return f"Frenando... Velocità attuale: {self.velocità} km/h"

    def info(self):
        return f"{self.marca} {self.modello} ({self.anno})"

# Classi derivate
class Auto(Veicolo):
    def __init__(self, marca, modello, anno, num_porte):
        super().__init__(marca, modello, anno)  # Chiama costruttore classe base
        self.num_porte = num_porte
        self.carburante = 100

    def rifornimento(self, litri):
        self.carburante = min(100, self.carburante + litri)
        return f"Rifornimento completato. Carburante: {self.carburante}%"

    def info(self):  # Override del metodo della classe base
        base_info = super().info()
        return f"{base_info} - {self.num_porte} porte"

class Moto(Veicolo):
    def __init__(self, marca, modello, anno, cilindrata):
        super().__init__(marca, modello, anno)
        self.cilindrata = cilindrata

    def impennata(self):
        if self.velocità > 30:
            return "Impennata eseguita! 🏍️"
        return "Velocità insufficiente per l'impennata"

    def info(self):
        base_info = super().info()
        return f"{base_info} - {self.cilindrata}cc"

# Utilizzo ereditarietà
auto = Auto("Toyota", "Corolla", 2022, 4)
moto = Moto("Yamaha", "R1", 2023, 1000)

print(auto.info())
print(moto.info())

# Polimorfismo
veicoli = [auto, moto]
for veicolo in veicoli:
    print(veicolo.accelera(50))  # Stesso metodo, comportamenti diversi
    print(veicolo.info())        # Metodo overridden in classi derivate

Metodi e Proprietà Speciali

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class ContoCorrente:
    def __init__(self, numero_conto, intestatario, saldo_iniziale=0):
        self.numero_conto = numero_conto
        self.intestatario = intestatario
        self._saldo = saldo_iniziale  # Attributo "privato"
        self._transazioni = []

    @property
    def saldo(self):
        """Getter per il saldo"""
        return self._saldo

    @saldo.setter
    def saldo(self, nuovo_saldo):
        """Setter per il saldo con validazione"""
        if nuovo_saldo < 0:
            raise ValueError("Il saldo non può essere negativo")
        self._saldo = nuovo_saldo

    def deposita(self, importo):
        if importo <= 0:
            raise ValueError("L'importo deve essere positivo")
        self._saldo += importo
        self._transazioni.append(f"Deposito: +{importo}€")
        return f"Depositati {importo}€. Saldo attuale: {self._saldo}€"

    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
        self._transazioni.append(f"Prelievo: -{importo}€")
        return f"Prelevati {importo}€. Saldo attuale: {self._saldo}€"

    def __str__(self):
        return f"Conto {self.numero_conto} - {self.intestatario}: {self._saldo}€"

    def __len__(self):
        return len(self._transazioni)

    def __getitem__(self, index):
        return self._transazioni[index]

    def __add__(self, altro_conto):
        """Somma saldi di due conti"""
        return self._saldo + altro_conto._saldo

    @staticmethod
    def calcola_interesse(capitale, tasso, anni):
        """Metodo statico per calcolare interessi"""
        return capitale * (1 + tasso) ** anni

    @classmethod
    def crea_conto_base(cls, intestatario):
        """Metodo di classe per creare conto con valori default"""
        import random
        numero = f"IT{random.randint(1000000, 9999999)}"
        return cls(numero, intestatario, 100)  # Saldo iniziale 100€

# Utilizzo classe avanzata
conto1 = ContoCorrente("IT1234567", "Mario Rossi", 1000)
conto2 = ContoCorrente.crea_conto_base("Luigi Verdi")

print(conto1.deposita(500))
print(conto1.preleva(200))
print(f"Saldo totale: {conto1 + conto2}€")
print(f"Numero transazioni: {len(conto1)}")
print(f"Ultima transazione: {conto1[-1]}")

# Calcolo interesse
interesse = ContoCorrente.calcola_interesse(1000, 0.05, 5)
print(f"Interesse composto su 1000€ al 5% per 5 anni: {interesse:.2f}€")

Gestione degli Errori

Try/Except Base

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# Gestione errori base
def dividi_numeri(a, b):
    try:
        risultato = a / b
        return risultato
    except ZeroDivisionError:
        return "Errore: Divisione per zero non permessa!"
    except TypeError:
        return "Errore: I parametri devono essere numeri!"

print(dividi_numeri(10, 2))   # 5.0
print(dividi_numeri(10, 0))   # Errore: Divisione per zero...
print(dividi_numeri(10, "a")) # Errore: I parametri devono essere numeri!

# Try/except con più eccezioni
def converti_e_calcola(stringa_numero, operazione):
    try:
        numero = float(stringa_numero)
        if operazione == "quadrato":
            return numero ** 2
        elif operazione == "radice":
            if numero < 0:
                raise ValueError("Non posso calcolare radice di numero negativo")
            return numero ** 0.5
        else:
            raise ValueError("Operazione non supportata")

    except ValueError as e:
        return f"Errore valore: {e}"
    except TypeError as e:
        return f"Errore tipo: {e}"
    except Exception as e:
        return f"Errore generico: {e}"

# Test funzione
print(converti_e_calcola("16", "quadrato"))  # 256.0
print(converti_e_calcola("16", "radice"))    # 4.0
print(converti_e_calcola("-4", "radice"))    # Errore valore: ...
print(converti_e_calcola("abc", "quadrato")) # Errore valore: ...

Gestione Avanzata degli Errori

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import logging

# Configurazione logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('app.log'),
        logging.StreamHandler()
    ]
)

class GestoreFile:
    def __init__(self, nome_file):
        self.nome_file = nome_file

    def leggi_file(self):
        try:
            with open(self.nome_file, 'r', encoding='utf-8') as file:
                contenuto = file.read()
                logging.info(f"File {self.nome_file} letto con successo")
                return contenuto

        except FileNotFoundError:
            logging.error(f"File {self.nome_file} non trovato")
            raise
        except PermissionError:
            logging.error(f"Permessi insufficienti per leggere {self.nome_file}")
            raise
        except UnicodeDecodeError:
            logging.error(f"Errore di encoding nel file {self.nome_file}")
            raise
        except Exception as e:
            logging.error(f"Errore imprevisto: {e}")
            raise
        finally:
            logging.info("Operazione di lettura terminata")

    def scrivi_file(self, contenuto, modalità='w'):
        try:
            with open(self.nome_file, modalità, encoding='utf-8') as file:
                file.write(contenuto)
                logging.info(f"Contenuto scritto nel file {self.nome_file}")
                return True

        except PermissionError:
            logging.error(f"Permessi insufficienti per scrivere {self.nome_file}")
            return False
        except IOError as e:
            logging.error(f"Errore I/O: {e}")
            return False
        except Exception as e:
            logging.error(f"Errore imprevisto durante scrittura: {e}")
            return False
        finally:
            logging.info("Operazione di scrittura terminata")

# Eccezioni personalizzate
class ErroreValidazione(Exception):
    """Eccezione personalizzata per errori di validazione"""
    pass

class ErroreAutenticazione(Exception):
    """Eccezione personalizzata per errori di autenticazione"""
    def __init__(self, messaggio, codice_errore):
        super().__init__(messaggio)
        self.codice_errore = codice_errore

def valida_utente(username, password):
    if len(username) < 3:
        raise ErroreValidazione("Username deve avere almeno 3 caratteri")

    if len(password) < 8:
        raise ErroreValidazione("Password deve avere almeno 8 caratteri")

    # Simulazione controllo credenziali
    if username != "admin" or password != "password123":
        raise ErroreAutenticazione("Credenziali non valide", 401)

    return True

# Utilizzo eccezioni personalizzate
def login_utente(username, password):
    try:
        valida_utente(username, password)
        return "Login effettuato con successo!"

    except ErroreValidazione as e:
        return f"Errore validazione: {e}"

    except ErroreAutenticazione as e:
        return f"Errore autenticazione: {e} (Codice: {e.codice_errore})"

print(login_utente("ab", "123"))           # Errore validazione
print(login_utente("user", "wrongpass"))  # Errore autenticazione
print(login_utente("admin", "password123"))  # Login effettuato

Moduli e Package

Creazione e Import Moduli

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File: matematica_utils.py
"""
Modulo con utilità matematiche
"""

import math

# Costanti
PI = 3.14159265359
EULERO = 2.71828182846

def area_cerchio(raggio):
    """Calcola l'area di un cerchio"""
    return PI * raggio ** 2

def circonferenza_cerchio(raggio):
    """Calcola la circonferenza di un cerchio"""
    return 2 * PI * raggio

def fattoriale(n):
    """Calcola il fattoriale di n"""
    if n < 0:
        raise ValueError("Il fattoriale non è definito per numeri negativi")
    if n == 0 or n == 1:
        return 1
    return n * fattoriale(n - 1)

def numeri_primi(limite):
    """Genera tutti i numeri primi fino al limite"""
    if limite < 2:
        return []

    primi = []
    for num in range(2, limite + 1):
        è_primo = True
        for i in range(2, int(math.sqrt(num)) + 1):
            if num % i == 0:
                è_primo = False
                break
        if è_primo:
            primi.append(num)

    return primi

class Calcolatrice:
    """Classe per operazioni matematiche avanzate"""

    def __init__(self):
        self.cronologia = []

    def calcola(self, operazione, a, b=None):
        if operazione == "somma":
            risultato = a + b
        elif operazione == "sottrazione":
            risultato = a - b
        elif operazione == "moltiplicazione":
            risultato = a * b
        elif operazione == "divisione":
            if b == 0:
                raise ValueError("Divisione per zero")
            risultato = a / b
        elif operazione == "potenza":
            risultato = a ** b
        elif operazione == "radice":
            if a < 0:
                raise ValueError("Radice di numero negativo")
            risultato = math.sqrt(a)
        else:
            raise ValueError("Operazione non supportata")

        self.cronologia.append(f"{operazione}: {risultato}")
        return risultato

    def stampa_cronologia(self):
        for operazione in self.cronologia:
            print(operazione)

if __name__ == "__main__":
    # Codice che viene eseguito solo quando il modulo è lanciato direttamente
    print("Test del modulo matematica_utils")
    print(f"Area cerchio raggio 5: {area_cerchio(5)}")
    print(f"Fattoriale di 5: {fattoriale(5)}")
    print(f"Numeri primi fino a 20: {numeri_primi(20)}")
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File: main.py - Utilizzo del modulo

# Diversi modi di importare
import matematica_utils
from matematica_utils import area_cerchio, PI
from matematica_utils import Calcolatrice as Calc
import matematica_utils as math_utils

# Utilizzo funzioni importate
print(f"Area cerchio: {area_cerchio(3)}")
print(f"PI: {PI}")

# Utilizzo modulo completo
print(f"Circonferenza: {matematica_utils.circonferenza_cerchio(3)}")
print(f"Primi fino a 30: {math_utils.numeri_primi(30)}")

# Utilizzo classe
calc = Calc()
print(calc.calcola("somma", 10, 5))
print(calc.calcola("radice", 16))
calc.stampa_cronologia()

# Import dinamico
import importlib

# Ricarica modulo se modificato
importlib.reload(matematica_utils)

# Import modulo per nome
nome_modulo = "matematica_utils"
modulo = importlib.import_module(nome_modulo)
print(modulo.area_cerchio(10))

Package e Strutture Complesse

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# Struttura package:
# mypackage/
# ├── __init__.py
# ├── core/
# │   ├── __init__.py
# │   ├── calcoli.py
# │   └── validazioni.py
# ├── utils/
# │   ├── __init__.py
# │   ├── file_utils.py
# │   └── string_utils.py
# └── tests/
#     ├── __init__.py
#     └── test_core.py

# File: mypackage/__init__.py
"""
Package MyPackage per utilità varie
"""

from .core.calcoli import CalcolatriceAvanzata
from .core.validazioni import valida_email, valida_telefono
from .utils.string_utils import pulisci_stringa, capitalizza_parole

__version__ = "1.0.0"
__author__ = "Il tuo nome"

# Definisce cosa viene importato con "from mypackage import *"
__all__ = [
    'CalcolatriceAvanzata',
    'valida_email',
    'valida_telefono',
    'pulisci_stringa',
    'capitalizza_parole'
]

# File: mypackage/core/calcoli.py
import math
from typing import List, Union

class CalcolatriceAvanzata:
    def __init__(self):
        self.memoria = 0
        self.cronologia = []

    def statistica(self, numeri: List[Union[int, float]]) -> dict:
        """Calcola statistiche base di una lista di numeri"""
        if not numeri:
            return {}

        risultato = {
            'count': len(numeri),
            'sum': sum(numeri),
            'mean': sum(numeri) / len(numeri),
            'min': min(numeri),
            'max': max(numeri)
        }

        # Mediana
        numeri_ordinati = sorted(numeri)
        n = len(numeri_ordinati)
        if n % 2 == 0:
            risultato['median'] = (numeri_ordinati[n//2-1] + numeri_ordinati[n//2]) / 2
        else:
            risultato['median'] = numeri_ordinati[n//2]

        # Deviazione standard
        media = risultato['mean']
        varianza = sum((x - media) ** 2 for x in numeri) / len(numeri)
        risultato['std_dev'] = math.sqrt(varianza)

        self.cronologia.append(f"Statistiche calcolate per {len(numeri)} numeri")
        return risultato

# File: mypackage/utils/string_utils.py
import re

def pulisci_stringa(testo: str) -> str:
    """Rimuove spazi extra e caratteri speciali"""
    # Rimuove caratteri non alfanumerici eccetto spazi e punteggiatura base
    testo_pulito = re.sub(r'[^\w\s.,!?-]', '', testo)
    # Rimuove spazi multipli
    testo_pulito = re.sub(r'\s+', ' ', testo_pulito)
    return testo_pulito.strip()

def capitalizza_parole(testo: str) -> str:
    """Capitalizza la prima lettera di ogni parola"""
    return ' '.join(parola.capitalize() for parola in testo.split())

def conta_parole(testo: str) -> dict:
    """Conta le occorrenze di ogni parola"""
    parole = testo.lower().split()
    conteggio = {}
    for parola in parole:
        parola_pulita = re.sub(r'[^\w]', '', parola)
        if parola_pulita:
            conteggio[parola_pulita] = conteggio.get(parola_pulita, 0) + 1
    return conteggio

# Utilizzo del package
if __name__ == "__main__":
    # Import dal package
    from mypackage import CalcolatriceAvanzata, pulisci_stringa
    from mypackage.utils.string_utils import conta_parole

    # Test calcolatrice
    calc = CalcolatriceAvanzata()
    dati = [1, 2, 3, 4, 5, 10, 15, 20]
    stats = calc.statistica(dati)

    for chiave, valore in stats.items():
        print(f"{chiave}: {valore}")

    # Test string utils
    testo_sporco = "  Questo è un    testo@#$ con caratteri    strani!!!  "
    testo_pulito = pulisci_stringa(testo_sporco)
    print(f"Testo pulito: '{testo_pulito}'")

    conteggio = conta_parole(testo_pulito)
    print(f"Conteggio parole: {conteggio}")

Questo è solo l’inizio della guida completa! Continuiamo con le sezioni rimanenti per coprire tutti gli aspetti avanzati di Python, dalla programmazione funzionale all’apprendimento automatico. Ogni sezione include esempi pratici, esercizi e progetti per consolidare le competenze acquisite.

La prossima parte coprirà:

  • File I/O e serializzazione
  • Decoratori e context manager
  • Programmazione funzionale
  • Multithreading e asyncio
  • Testing e debugging
  • Data science con NumPy, Pandas
  • Web development con Flask e Django
  • Machine learning con scikit-learn
  • Progetti pratici completi

Vuoi che continui con una sezione specifica o procedo in ordine con il file I/O e la serializzazione?