Ciao e bentornato!
Introduzione
Nella lezione precedente abbiamo esplorato il "perché" dell'immutabilità , scoprendo come essa migliori la correttezza del programma e semplifichi drasticamente la gestione della concorrenza. Abbiamo capito che evitare mutazioni nascoste rende il codice più prevedibile e robusto.
Oggi passiamo dalla teoria alla pratica. L'obiettivo di questa lezione è imparare a usare le strutture dati immutabili native di Python, tuple e frozenset. Vedremo come queste ci permettono di applicare i concetti di immutabilità che abbiamo appena appreso, scrivendo codice che è immutabile by design. Inizieremo a costruire le fondamenta pratiche del nostro stile di programmazione funzionale.
1. Il tuple: una sequenza immutabile
La prima e più comune struttura dati immutabile che incontreremo è la tuple. Puoi pensarla come la controparte immutabile della list. Come una lista, una tupla è una sequenza ordinata di elementi a cui si può accedere tramite un indice. La differenza cruciale è che, una volta creata, non può essere modificata.
Python's Mutable vs Immutable Types: What's the Difference?
Per una definizione formale e alcuni esempi di base, leggi la sezione dedicata alle tuple nell'articolo di Real Python.
Leggi la sezione intitolata "Tuples". Concentrati su come si creano le tuple e sull'errore (TypeError) che si ottiene quando si tenta di modificare un elemento.
Questo sembra semplice, ma c'è una sottigliezza fondamentale che è cruciale da capire per un programmatore esperto. L'immutabilità di una tupla è "superficiale" (shallow). La tupla stessa non può essere modificata, ma cosa succede se uno dei suoi elementi è un oggetto mutabile, come una lista?
Il seguente video spiega questo "paradosso" in modo eccezionalmente chiaro.
Immutable vs Mutable Objects in Python
Questo video di Real Python mostra una situazione che spesso confonde i neofiti di Python: una tupla immutabile che contiene una lista mutabile.
Guarda la sezione dal timestamp 05:31 al 09:19. Presta molta attenzione a come è possibile modificare la lista contenuta nella tupla e perché, dal punto di vista di Python, questo non viola l'immutabilità della tupla stessa.
Questo concetto di immutabilità non transitiva è vitale. Significa che per garantire una vera immutabilità , non solo il contenitore (la tupla) deve essere immutabile, ma anche tutti i suoi contenuti devono esserlo.
Costruire una struttura dati veramente immutabile
Come possiamo applicare questo principio in un caso reale? Immagina di avere una lista di dizionari, una struttura dati molto comune in Python. Sia le liste che i dizionari sono mutabili, rendendo l'intera struttura vulnerabile a modifiche accidentali.
Il video seguente mostra come trasformare questa struttura dati mutabile in una versione completamente immutabile, combinando tuple con un'altra utile utility, collections.namedtuple.
Functional Programming in Python: Immutable Data Structures
Vediamo ora un esempio pratico di refactoring verso l'immutabilità . Questo video di Real Python trasforma una lista di dizionari in una tupla di namedtuple.
Guarda il video dal timestamp 05:30 al 16:45. Segui questi passaggi: Il problema (05:30 - 09:47): Osserva come la struttura list[dict] sia mutabile e perché questo sia un problema nello stile funzionale. Una soluzione parziale (10:41 - 13:33): Vedi come collections.namedtuple viene usato per creare record immutabili per i singoli scienziati, risolvendo il problema della mutabilità dei dizionari. La soluzione completa (13:33 - 16:45): Infine, osserva come questi namedtuple immutabili vengano inseriti in una tuple, creando una collezione esterna anch'essa immutabile. Il risultato finale è una tuple[Scientist], una struttura dati robusta e completamente immutabile.
Questo esempio è un ottimo modello di come si pensa in modo funzionale: invece di usare strutture generiche e mutabili, si definiscono tipi di dati specifici e immutabili che modellano il dominio del problema in modo più sicuro e chiaro.
2. Il frozenset: un insieme immutabile
Proprio come la tupla è la versione immutabile della lista, Python fornisce il frozenset come controparte immutabile del set.

Un frozenset è una collezione non ordinata di elementi unici che, una volta creata, non può essere alterata. Non puoi aggiungere o rimuovere elementi. Allora perché usarlo?
La sua immutabilità gli conferisce una proprietà fondamentale che un set normale non ha: è hashable. Questo significa che può essere usato in contesti dove è richiesto un valore immutabile e calcolabile, come la chiave di un dizionario o un elemento all'interno di un altro set.
L'articolo seguente di DataCamp fornisce un'eccellente panoramica del frozenset.
Python Frozenset: Why It Exists and When You Need It
Questo articolo spiega in dettaglio cos'è un frozenset, perché esiste e quando dovresti usarlo.
Leggi le seguenti sezioni per ottenere una comprensione completa: What Is a frozenset in Python?: Una definizione di base. Frozenset vs. Other Python Collections: Un confronto chiave con set e tuple che chiarisce quando usare l'uno o l'altro. Python Frozenset and Hashability: La sezione più importante, che spiega perché l'immutabilità porta all'hashability e mostra l'esempio pratico dell'uso di frozenset come chiave di un dizionario. Common Use Cases for frozenset: Esempi pratici come la rappresentazione di permessi o configurazioni fisse.
Esempio pratico: set vs frozenset come chiave
Proviamo a vedere il concetto di hashability in azione. Esegui questo codice nella tua console Python:
# Un set mutabile NON può essere una chiave di dizionario
permissions_set = {"read", "write"}
user_permissions = {permissions_set: "Admin"}
# Output: TypeError: unhashable type: 'set'
# Un frozenset immutabile PUÃ’ essere una chiave di dizionario
permissions_frozenset = frozenset({"read", "write"})
user_permissions = {permissions_frozenset: "Admin"}
print(user_permissions)
# Output: {frozenset({'write', 'read'}): 'Admin'}
Questo dimostra il principale vantaggio pratico del frozenset: permette di usare una collezione di tipo "set" in contesti che richiedono immutabilità .
Esercizio di riflessione
Immagina di dover modellare i dati di un prodotto per un sito di e-commerce. Un prodotto ha le seguenti informazioni:
- Un
product_idunivoco (es.12345). - Un
name(es."Tastiera Meccanica"). - Una lista di
category_tags(es.["elettronica", "periferiche", "gaming"]). Queste categorie sono fisse per il prodotto. - Una cronologia dei
price_changes, che è una lista di tuple(timestamp, prezzo). Questa cronologia cresce nel tempo.
Rifletti su quali strutture dati (mutabili o immutabili) useresti per rappresentare un singolo prodotto e le sue proprietà . Non c'è un'unica risposta giusta, ma giustifica le tue scelte in base ai principi di immutabilità e all'intento che vuoi comunicare con il tuo codice.
Clicca per vedere una possibile soluzione
Una possibile modellazione potrebbe essere questa:
product_idename: Essendointestr, sono già immutabili.category_tags: Poiché le categorie sono fisse e il loro ordine non è importante, unfrozensetè una scelta eccellente. Comunica chiaramente l'intento che questi tag sono un gruppo immutabile e unico di proprietà .price_changes: Poiché la cronologia cresce nel tempo, unalistmutabile è appropriata. Tuttavia, gli elementi al suo interno ((timestamp, prezzo)) dovrebbero esseretuple, poiché ogni singola voce di prezzo non dovrebbe cambiare.
Potresti ulteriormente incapsulare le proprietà immutabili in un namedtuple per creare un record di prodotto chiaro e sicuro:
from collections import namedtuple
Product = namedtuple("Product", ["product_id", "name", "category_tags"])
# Dati di esempio
product_record = Product(
product_id=12345,
name="Tastiera Meccanica",
category_tags=frozenset(["elettronica", "periferiche", "gaming"])
)
price_history = [
("2023-01-01T10:00:00Z", 120.00),
("2023-02-15T14:30:00Z", 115.50)
]
# Aggiunta di una nuova variazione di prezzo (mutazione controllata)
price_history.append(("2023-03-01T09:00:00Z", 118.00))
print(product_record)
print(price_history)
In questo modello, i dati "statici" del prodotto sono protetti dalla mutazione, mentre i dati "dinamici" (la cronologia dei prezzi) possono essere modificati in modo esplicito e controllato.
Conclusione
In questa lezione abbiamo messo in pratica il concetto di immutabilità usando gli strumenti che Python ci offre.
Punti chiave:
tupleè la controparte immutabile dellalist, usata per sequenze ordinate e fisse.frozensetè la controparte immutabile delset, usato per collezioni non ordinate e uniche, con il vantaggio di essere hashable e quindi utilizzabile come chiave di dizionario.- L'immutabilità in Python è "superficiale": una tupla può contenere oggetti mutabili. Per una vera immutabilità , tutti gli elementi annidati devono essere immutabili.
- La scelta tra
tupleefrozensetdipende dall'intento:tupleper la struttura posizionale,frozensetper la verifica di appartenenza.
Abbiamo capito il "perché" dell'immutabilità e ora abbiamo visto il "come" implementarla con le strutture dati di base. Il passo successivo è unire questi concetti.
Nella prossima lezione, rinnoveremo (refactoring) del codice imperativo esistente per utilizzare questi pattern di dati immutabili, trasformando funzioni con effetti collaterali in funzioni pure che operano su tuple e frozenset.