Una volta che le list comprehension fanno clic, non scriverai mai più un ciclo for che aggiunge elementi a una lista.
Non sono solo zucchero sintattico — segnalano l'intento in modo chiaro e girano più veloce del ciclo equivalente in CPython
grazie all'ottimizzazione del bytecode. Secondo il
tutorial ufficiale di Python,
le comprehension forniscono un modo conciso per creare liste basate su sequenze esistenti o altri iterabili.
Questo articolo costruisce prima il modello mentale, poi copre ogni pattern reale: filtraggio, annidamento,
comprehension di dizionari e set, e il caso in cui dovresti usare un normale ciclo for.
Il Pattern di Base
L'anatomia di una list comprehension è [espressione for elemento in iterabile].
Tre parti: l'espressione di output (cosa diventa ogni elemento), la variabile del ciclo
e l'iterabile da cui prelevare. Inizia con un ciclo familiare e riducilo.
# Plain for loop — building a list of file sizes in KB
file_sizes_bytes = [1024, 204800, 51200, 3145728, 8192]
sizes_kb = []
for size in file_sizes_bytes:
sizes_kb.append(size / 1024)
# print(sizes_kb) → [1.0, 200.0, 50.0, 3072.0, 8.0]
# Same result as a list comprehension — one line, same meaning
sizes_kb = [size / 1024 for size in file_sizes_bytes]La comprehension si legge quasi come l'inglese: "dammi size / 1024 per ogni size
in file_sizes_bytes." Quella chiarezza è il vero vantaggio — un lettore non deve tracciare una
chiamata append per capire cosa stai costruendo.
# Another common pattern: deriving one list from another
usernames = ["alice", "bob", "carol"]
# Build a list of display names
display_names = [name.capitalize() for name in usernames]
# ['Alice', 'Bob', 'Carol']
# Or extract a single field from a list of dicts
users = [
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "viewer"},
{"id": 3, "name": "Carol", "role": "editor"},
]
names = [user["name"] for user in users]
# ['Alice', 'Bob', 'Carol']Filtraggio con if
Aggiungi una condizione alla fine e solo gli elementi che passano entrano nella lista di output:
[espressione for elemento in iterabile if condizione].
Questo è il pattern che sostituisce una combinazione for + if + append.
# Loop version — extract only active users
active_users = []
for user in users:
if user["active"]:
active_users.append(user["name"])
# Comprehension version — identical result
users = [
{"name": "Alice", "active": True},
{"name": "Bob", "active": False},
{"name": "Carol", "active": True},
{"name": "Dave", "active": False},
]
active_users = [user["name"] for user in users if user["active"]]
# ['Alice', 'Carol']# Filtering a raw CSV row — drop blanks and whitespace-only values
raw_row = ["[email protected]", "", " ", "editor", " "]
clean_row = [field.strip() for field in raw_row if field.strip()]
# ['[email protected]', 'editor']
# Filtering a list of log levels
log_lines = [
"INFO server started",
"DEBUG loading config",
"ERROR database timeout",
"DEBUG query took 450ms",
"ERROR disk space low",
]
errors = [line for line in log_lines if line.startswith("ERROR")]
# ['ERROR database timeout', 'ERROR disk space low']Lavorare con le Stringhe
L'elaborazione delle stringhe è dove le comprehension dimostrano il loro valore. La combinazione dei ricchi metodi per stringhe di Python e la sintassi delle comprehension mantiene le pipeline di trasformazione leggibili senza variabili intermedie.
# Strip whitespace from tags coming out of a form field
raw_tags = [" python ", "data science", " machine-learning ", "API"]
tags = [tag.strip().lower() for tag in raw_tags]
# ['python', 'data science', 'machine-learning', 'api']
# Normalise email addresses from a signup CSV
raw_emails = ["[email protected]", " [email protected] ", "[email protected]"]
emails = [e.strip().lower() for e in raw_emails]
# ['[email protected]', '[email protected]', '[email protected]']
# Extract file extensions from a list of uploaded filenames
filenames = ["report.pdf", "avatar.PNG", "data.CSV", "archive.tar.gz", "notes.txt"]
extensions = [name.rsplit(".", 1)[-1].lower() for name in filenames if "." in name]
# ['pdf', 'png', 'csv', 'gz', 'txt']" [email protected] " supera un controllo .lower() sul contenuto ma porta ancora
spazi iniziali che romperanno una ricerca nel database o la corrispondenza di chiavi JSON.Comprehension di Dizionari e Set
La stessa idea si estende a dizionari e set. Una
dict comprehension
usa le parentesi graffe con una coppia chiave: valore: {chiave: valore for elemento in iterabile}.
Una set comprehension elimina i due punti e produce una collezione deduplicata:
{espressione for elemento in iterabile}.
# Invert a dict — swap keys and values
permissions = {"alice": "admin", "bob": "viewer", "carol": "editor"}
by_role = {role: name for name, role in permissions.items()}
# {'admin': 'alice', 'viewer': 'bob', 'editor': 'carol'}
# Build a fast lookup dict from a list of user records
users = [
{"id": 101, "name": "Alice", "active": True},
{"id": 102, "name": "Bob", "active": False},
{"id": 103, "name": "Carol", "active": True},
]
# O(1) lookups by ID — much faster than scanning the list every time
user_by_id = {user["id"]: user for user in users}
# {101: {...}, 102: {...}, 103: {...}}
# Access a user directly
user_by_id[102]["name"] # 'Bob'# Set comprehension — deduplicate a list of file extensions
uploads = ["report.pdf", "data.csv", "summary.pdf", "export.CSV", "notes.txt"]
unique_extensions = {name.rsplit(".", 1)[-1].lower() for name in uploads if "." in name}
# {'pdf', 'csv', 'txt'} — order not guaranteedUna precisazione sui set: l'ordine non è preservato. Se hai bisogno di deduplicare una lista mantenendo
l'ordine originale, una set comprehension è lo strumento sbagliato — usa
list(dict.fromkeys(elementi)) invece, che sfrutta il comportamento ordinato per inserimento dei
dizionari in Python 3.7+.
Comprehension Annidate
Puoi annidare le comprehension per iterare su strutture annidate. Il caso d'uso più comune è appiattire una lista di liste — una matrice da un'analisi CSV, pagine di risposta API in blocchi, o risultati di query raggruppati.
# Flatten a 2D list (e.g. paginated API results)
pages = [
[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}],
[{"id": 3, "name": "Carol"}],
[{"id": 4, "name": "Dave"}, {"id": 5, "name": "Eve"}],
]
all_users = [user for page in pages for user in page]
# [{'id': 1, ...}, {'id': 2, ...}, {'id': 3, ...}, {'id': 4, ...}, {'id': 5, ...}]
# The order mirrors what nested for loops would produce:
# for page in pages:
# for user in page:
# all_users.append(user)Leggi le comprehension annidate da sinistra a destra — il ciclo esterno viene prima, il ciclo interno secondo.
Corrisponde all'ordine dei cicli for annidati.
# Two levels — fine and readable
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [cell for row in matrix for cell in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Three levels — stop here. Use a plain loop or a helper function.
# This is the point where readability dies:
cube = [[[1,2],[3,4]],[[5,6],[7,8]]]
# Don't do this:
flat3 = [val for layer in cube for row in layer for val in row]
# Do this instead:
flat3 = []
for layer in cube:
for row in layer:
flat3.extend(row)(user["id"] for user in users). Vengono valutate lazily — gli elementi vengono prodotti uno alla volta
invece di costruire l'intera lista in memoria. Usale quando hai bisogno di iterare solo una volta, o quando stai
passando il risultato direttamente a sum(), max(), any() o funzioni simili.
Vedi il riferimento alle espressioni generatrici per tutti i dettagli.Il Ternario if/else Dentro una Comprehension
Quando hai bisogno di trasformare elementi in uno di due valori — piuttosto che eliminarli — usa un'espressione ternaria nella posizione di output. La posizione conta: il ternario appartiene all'inizio, non dopo l'iterabile.
# Correct: ternary is the output expression
users = [
{"name": "Alice", "active": True},
{"name": "Bob", "active": False},
{"name": "Carol", "active": True},
]
status = ["active" if user["active"] else "inactive" for user in users]
# ['active', 'inactive', 'active']
# Normalise a config value — replace None with a default
raw_config = ["/var/log", None, "/tmp", None, "/etc/app"]
paths = [path if path is not None else "/var/log/default" for path in raw_config]
# ['/var/log', '/var/log/default', '/tmp', '/var/log/default', '/etc/app']# Common mistake — putting the ternary after the iterable (SyntaxError)
# status = ["active" for user in users if user["active"] else "inactive"] # WRONG
# The trailing "if" is a filter — it drops non-matching items entirely.
# The ternary ("if ... else ...") in the output expression transforms all items.
# They serve different purposes. You can combine them:
# Keep only users, but show their status
status = ["active" if user["active"] else "inactive"
for user in users
if user["name"] != "Bob"]
# ['active', 'active'] — Bob was filtered out entirelyQuando Usare un Ciclo for invece
Le list comprehension sono per costruire liste. Se stai davvero eseguendo
effetti collaterali, usa un ciclo for — non una comprehension. Questo conta oltre lo stile: una comprehension
che scarta il suo risultato spreca memoria costruendo una lista che nessuno usa, e nasconde l'intento al lettore.
# Bad — comprehension for side effects (writing to a file, printing, calling an API)
[print(f"Processing user {user['name']}") for user in users] # don't do this
[requests.post("/api/notify", json=user) for user in users] # definitely don't do this
# Good — plain for loop makes the intent obvious
for user in users:
print(f"Processing user {user['name']}")
for user in users:
requests.post("/api/notify", json=user)# Bad — comprehension with logic complex enough to need comments or multiple steps
result = [
user["name"].strip().lower()
if user.get("active") and user.get("email_verified")
else user["name"].strip().lower() + " (unverified)"
for user in users
if user.get("role") in ("admin", "editor") and user.get("last_login") is not None
]
# Good — break it out when the logic is this involved
result = []
for user in users:
if user.get("role") not in ("admin", "editor"):
continue
if user.get("last_login") is None:
continue
name = user["name"].strip().lower()
if not (user.get("active") and user.get("email_verified")):
name += " (unverified)"
result.append(name)- Usa una comprehension quando stai costruendo una nuova lista da un iterabile esistente con un'espressione chiara e un filtro opzionale.
- Usa un ciclo for quando esegui effetti collaterali — I/O, chiamate di rete, stampe, mutazione dello stato esterno.
- Usa un ciclo for quando la logica di trasformazione ha bisogno di più righe, variabili intermedie o commenti per essere compresa.
- Usa un'espressione generatrice quando hai bisogno di iterare solo una volta o passare direttamente a
sum(),any(),max()— nessuna lista necessaria in memoria.
Nota sulle Prestazioni
Le list comprehension sono significativamente più veloci di un ciclo for + append
equivalente in CPython. Il motivo è il bytecode: una comprehension viene compilata in un opcode dedicato LIST_APPEND
che evita la ricerca dell'attributo su list.append a ogni iterazione.
Il wiki dei consigli sulle prestazioni di Python
lo tratta, e il divario è tipicamente del 10–40% per i carichi di lavoro Python puro a seconda della dimensione della lista.
import timeit
data = list(range(100_000))
# for + append
def with_loop():
result = []
for x in data:
result.append(x * 2)
return result
# list comprehension
def with_comprehension():
return [x * 2 for x in data]
# generator expression — no list built at all
def with_generator():
return sum(x * 2 for x in data)
# Typical results on CPython 3.12:
# with_loop(): ~7.2 ms
# with_comprehension(): ~4.8 ms (~33% faster)
# with_generator(): ~4.1 ms (and uses O(1) memory vs O(n))Se non hai bisogno di una lista materializzata — stai passando il risultato a sum(),
any(), max(), o iterandola una volta — usa una
espressione generatrice
invece. Usa memoria costante indipendentemente dalla dimensione dell'input, il che conta quando si elaborano grandi esportazioni CSV
o payload JSON in un ciclo stretto.
Conclusione
Le list comprehension sono una di quelle funzionalità di Python che sembrano scomode per circa una settimana e poi diventano impossibili da abbandonare. Il modello mentale è semplice: espressione di output, variabile del ciclo, iterabile, filtro opzionale. Attieniti a questo e scriverai Python leggibile e idiomatico. Quando la logica diventa complessa, questo è il segnale per tornare a un normale ciclo — non un fallimento, solo lo strumento giusto per il lavoro.
Se lavori con dati JSON in Python — trasformando risposte API in liste di valori, estraendo campi da record, costruendo dizionari di ricerca — gli strumenti su questo sito si abbinano bene a ciò che hai appena imparato. Prova il JSON Formatter per ispezionare e formattare i payload JSON prima di scrivere la comprehension che li elabora, o il CSV Formatter per convalidare i dati CSV prima di analizzarli in una lista di righe. Per il riferimento completo al linguaggio, la documentazione Python sulle list comprehension e la PEP 202 (la proposta originale) vale la pena leggere.