Python enumerate, zip, map e filter
Scopri come funzionano enumerate, zip, map e filter in Python, con esempi chiari e pattern pratici per un codice di iterazione più leggibile.
Python include quattro funzioni built-in — enumerate, zip, map e filter — che sostituiscono i pattern comuni dei cicli con un'unica espressione leggibile. Ognuna restituisce un iteratore (lazy, efficiente in memoria) e si combina naturalmente con i cicli for e le list comprehension.
Questo capitolo tratta:
enumerate()— ciclo con indice automaticozip()— iterare su due o più sequenze in parallelomap()— applicare una funzione a ogni elementofilter()— mantenere solo gli elementi che superano un test- Combinare tutte e quattro in pattern reali
- Quando preferire una list comprehension
enumerate() — indice durante l'iterazione
Quando si ha bisogno sia della posizione sia del valore durante un ciclo, l'approccio ingenuo usa un contatore manuale:
colors = ["red", "green", "blue"]
i = 0
for color in colors:
print(i, color)
i += 1enumerate() lo fa automaticamente. Avvolge qualsiasi iterabile e produce coppie (index, value):
colors = ["red", "green", "blue"]
for i, color in enumerate(colors):
print(i, color)Output:
0 red
1 green
2 bluePartire da un indice diverso
Passa un argomento start per iniziare il conteggio da qualsiasi intero:
fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")Output:
1. apple
2. banana
3. cherryQuesto è utile quando si costruiscono liste numerate da visualizzare, dove la numerazione a base 0 risulta strana per il lettore.
enumerate() con una condizione
Poiché enumerate() è semplicemente un iteratore ordinario, puoi aggiungere test if all'interno del corpo del ciclo:
scores = [72, 91, 55, 88, 44]
for rank, score in enumerate(scores, start=1):
if score >= 80:
print(f"Rank {rank}: score {score} — PASS")Output:
Rank 2: score 91 — PASS
Rank 4: score 88 — PASSOttenere solo l'indice senza enumerate()
Se hai bisogno solo di un indice e nient'altro, range(len(seq)) funziona — ma non appena hai bisogno anche del valore, usa enumerate() per evitare di scrivere seq[i] ripetutamente.
zip() — iterare su più sequenze in parallelo
zip() combina due o più iterabili in un unico iteratore di tuple, abbinando gli elementi alle posizioni corrispondenti:
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")Output:
Alice: 95
Bob: 87
Charlie: 92zip() con tre o più iterabili
first = ["a", "b", "c"]
second = [1, 2, 3]
third = [True, False, True]
for x, y, z in zip(first, second, third):
print(x, y, z)Output:
a 1 True
b 2 False
c 3 TrueSequenze di lunghezza diversa
zip() si ferma all'iterabile più corto e scarta silenziosamente gli elementi rimanenti da quelli più lunghi:
short = [1, 2]
long = [10, 20, 30, 40]
print(list(zip(short, long)))Output:
[(1, 10), (2, 20)]I valori 30 e 40 da long vengono scartati. Se hai bisogno di conservare tutti gli elementi, usa itertools.zip_longest dalla libreria standard:
from itertools import zip_longest
short = [1, 2]
long = [10, 20, 30, 40]
print(list(zip_longest(short, long, fillvalue=0)))Output:
[(1, 10), (2, 20), (0, 30), (0, 40)]Convertire il risultato di zip in una lista o un dizionario
zip() restituisce un iteratore lazy. Avvolgilo in list() per materializzare tutte le coppie contemporaneamente, oppure passalo a dict() per costruire una mappatura da due sequenze parallele:
keys = ["name", "age", "city"]
values = ["Alice", 30, "Paris"]
record = dict(zip(keys, values))
print(record)Output:
{'name': 'Alice', 'age': 30, 'city': 'Paris'}Decomprimere con zip(*...)
Il trucco di unpacking * "inverte" una sequenza di tuple separandole in sequenze distinte:
pairs = [(1, "a"), (2, "b"), (3, "c")]
numbers, letters = zip(*pairs)
print(numbers) # (1, 2, 3)
print(letters) # ('a', 'b', 'c')map() — applicare una funzione a ogni elemento
map(function, iterable) applica function a ogni elemento e restituisce un iteratore lazy dei risultati. Sostituisce un ciclo for che costruisce una nuova lista trasformando ogni elemento.
numbers = [1, 2, 3, 4, 5]
squares = map(lambda x: x ** 2, numbers)
print(list(squares))Output:
[1, 4, 9, 16, 25]map() con una funzione con nome
Puoi passare qualsiasi callable — una funzione built-in, una funzione definita con def o una lambda:
words = [" hello ", " world ", " python "]
stripped = list(map(str.strip, words))
print(stripped)Output:
['hello', 'world', 'python']Qui str.strip viene passata come riferimento (senza parentesi). map() chiama str.strip(word) per ogni elemento.
map() su più iterabili
Passa iterabili aggiuntivi per mappare una funzione che accetta due o più argomenti. map() scorre tutti gli iterabili in parallelo (come zip(), si ferma al più corto):
a = [1, 2, 3]
b = [10, 20, 30]
totals = list(map(lambda x, y: x + y, a, b))
print(totals)Output:
[11, 22, 33]map() vs list comprehension
Entrambi gli approcci sono validi. La regola generale:
| Situazione | Preferire |
|---|---|
| Applicare una funzione con nome già esistente | map() — più breve, nessuna lambda necessaria |
| Scrivere un'espressione inline | List comprehension — più leggibile |
| Più input in parallelo | map() con più iterabili |
Forme equivalenti:
# map() with a named function
result = list(map(str.upper, ["a", "b", "c"]))
# list comprehension — same result
result = [s.upper() for s in ["a", "b", "c"]]filter() — mantenere solo gli elementi che corrispondono a una condizione
filter(function, iterable) mantiene gli elementi per cui function restituisce un valore truthy. Restituisce un iteratore lazy.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens))Output:
[2, 4, 6, 8, 10]filter() con una funzione con nome
def is_positive(n):
return n > 0
values = [-3, -1, 0, 4, 7, -2]
positives = list(filter(is_positive, values))
print(positives)Output:
[4, 7]filter(None, iterable) — rimuovere i valori falsy
Passare None come funzione mantiene solo gli elementi truthy. Questo è un modo rapido per eliminare stringhe vuote, zeri e valori None:
mixed = [0, "hello", "", None, 42, False, "world"]
truthy = list(filter(None, mixed))
print(truthy)Output:
['hello', 42, 'world']filter() vs list comprehension
filter() e una list comprehension condizionale svolgono lo stesso compito:
numbers = range(1, 11)
# filter()
evens_f = list(filter(lambda x: x % 2 == 0, numbers))
# list comprehension — equivalent
evens_c = [x for x in numbers if x % 2 == 0]
print(evens_f) # [2, 4, 6, 8, 10]
print(evens_c) # [2, 4, 6, 8, 10]Preferisci una list comprehension quando la condizione è scritta inline; preferisci filter() quando hai già una funzione predicato ben nominata.
Combinare enumerate, zip, map e filter
Queste quattro funzioni si compongono naturalmente. Ecco un esempio realistico: hai due liste parallele di nomi di studenti e punteggi grezzi; vuoi stampare un report numerato che include solo gli studenti che hanno superato l'esame (punteggio >= 60), con i punteggi convertiti in voti letterali.
def letter_grade(score):
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
elif score >= 60:
return "D"
return "F"
names = ["Alice", "Bob", "Carol", "Dave", "Eve"]
scores = [85, 52, 91, 67, 48]
# Step 1: pair names with scores
pairs = zip(names, scores)
# Step 2: keep only passing students
passing = filter(lambda pair: pair[1] >= 60, pairs)
# Step 3: convert scores to letter grades
graded = map(lambda pair: (pair[0], letter_grade(pair[1])), passing)
# Step 4: number the results
for rank, (name, grade) in enumerate(graded, start=1):
print(f"{rank}. {name}: {grade}")Output:
1. Alice: B
2. Carol: A
3. Dave: DOgni passaggio produce un iteratore; nulla viene materializzato in una lista finché il ciclo for non lo consuma. Questo mantiene l'utilizzo della memoria costante indipendentemente dalla dimensione dell'input.
Errori comuni
Gli iteratori si esauriscono dopo un solo passaggio
map(), filter() e zip() restituiscono tutti iteratori, non liste. Una volta consumato un iteratore, è vuoto:
squares = map(lambda x: x ** 2, [1, 2, 3])
print(list(squares)) # [1, 4, 9]
print(list(squares)) # [] — already exhaustedAvvolgi in list() in anticipo se devi iterare più di una volta.
zip() scarta silenziosamente gli elementi finali
Quando le sequenze di input hanno lunghezze diverse, zip() scarta gli elementi extra senza avvisi. Controlla sempre esplicitamente le lunghezze se questo è importante, oppure usa itertools.zip_longest.
enumerate() non modifica l'iterabile
enumerate() avvolge l'iterabile originale; non lo copia. Modificare la lista sottostante durante l'iterazione può produrre risultati inattesi — la stessa cautela che si applica ai normali cicli for.
Riferimento rapido
| Funzione | Firma | Cosa restituisce |
|---|---|---|
enumerate | enumerate(iterable, start=0) | Iteratore di tuple (index, value) |
zip | zip(*iterables) | Iteratore di tuple, un elemento da ogni iterabile per tupla |
map | map(function, *iterables) | Iteratore di function applicata a ogni elemento |
filter | filter(function, iterable) | Iteratore degli elementi in cui function restituisce True |
Tutte e quattro sono lazy: producono valori su richiesta invece di costruire una lista completa in anticipo.