ProgrammazioneSviluppatore Backend

Come funziona la funzione incorporata zip() in Python, a cosa serve e quali sono i punti delicati nella gestione di sequenze di lunghezza diversa?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

Storia della domanda

La funzione zip() è comparsa già in Python 2 (allora restituiva una lista), mentre in Python 3 restituisce un iteratore pigro. Essa "cucisce" insieme più sequenze in tuple elemento per elemento, rendendo la gestione di collezioni iterabili parallele comoda ed efficiente.

Problema

Spesso è necessario elaborare più elenchi (o altre sequenze) contemporaneamente - ad esempio, iterare su coppie chiave-valore o gestire le coordinate punto-coppia. La sincronizzazione manuale degli indici è fonte di errori e scarsa leggibilità del codice, specialmente per collezioni di lunghezza diversa.

Soluzione

La funzione zip() accetta qualsiasi numero di oggetti iterabili e restituisce un iteratore di tuple, ognuna delle quali contiene gli elementi corrispondenti di ciascun iterabile. Se le sequenze sono di lunghezza diversa, il risultato si interrompe sulla più corta.

Esempio di codice:

names = ['Alice', 'Bob', 'Charlie'] ages = [24, 27, 30] for name, age in zip(names, ages): print(f'{name} ha {age} anni')

È possibile disimballare zip usando *:

pairs = [(1, 'a'), (2, 'b'), (3, 'c')] nums, chars = zip(*pairs) print(nums) # (1, 2, 3) print(chars) # ('a', 'b', 'c')

Caratteristiche chiave:

  • zip() restituisce un iteratore (in Python 3), non una lista.
  • L'operazione zip() si interrompe al più corto iterabile.
  • Consente l'elaborazione parallela delle collezioni senza un controllo esplicito degli indici.

Domande insidiose.

Cosa succede se si passa a zip() collezioni di lunghezza diversa?

zip() si fermerà quando raggiungerà la fine della collezione più corta - gli elementi rimanenti delle collezioni più lunghe verranno ignorati.

print(list(zip([1,2,3], ['a','b']))) # [(1, 'a'), (2, 'b')]

Come ottenere tuple riempiendo le sequenze più corte con un valore predefinito?

La zip() standard non lo fa, ma esiste itertools.zip_longest per questo comportamento:

from itertools import zip_longest for a, b in zip_longest([1,2], ['x','y','z'], fillvalue=None): print(a, b) # 1 x # 2 y # None z

È possibile "scomporre" il risultato di zip(), per ottenere nuovamente le liste originali?

Sì, se tutte le collezioni originali avevano la stessa lunghezza e il risultato non è stato modificato, l'operatore * consente di disimballare zip.

pairs = [(1,2), (3,4)] a, b = zip(*pairs) print(a) # (1, 3) print(b) # (2, 4)

Errori comuni e anti-pattern

  • Aspettarsi che zip() arrivi sempre alla fine della collezione più lunga.
  • Presumere che in Python 3 zip() restituisca una lista (è un iteratore, a volte è necessario racchiudere in list()).
  • Lavorare con zip su fonti modificabili, che vengono consumate ad ogni iterazione.

Esempio dalla vita reale

Caso negativo

Gestire collezioni correlate di lunghezza diversa, senza considerare le peculiarità di zip:

lst1 = [1,2,3,4] lst2 = ['a','b'] for x, y in zip(lst1, lst2): print(x, y) # 1 a # 2 b # (3,4) e 'c', 'd' da lst1 non sono stati elaborati

Pro:

  • Facile e chiaro, se le sequenze sono garantite della stessa lunghezza.

Contro:

  • Perdita di valori se la lunghezza effettiva delle collezioni è diversa.

Caso positivo

Utilizzo di zip_longest con fillvalue, per non perdere nessun elemento:

from itertools import zip_longest lst1 = [1,2,3,4] lst2 = ['a','b'] for x, y in zip_longest(lst1, lst2, fillvalue='?'): print(x, y) # 1 a # 2 b # 3 ? # 4 ?

Pro:

  • Garanzia di elaborazione di tutti gli elementi.
  • Possibilità di specificare esplicitamente un valore "vuoto".

Contro:

  • Necessità di importare un modulo esterno.
  • È importante non dimenticare fillvalue, altrimenti si ottiene None per default.