Come funziona zip (* [iter (s)] * n) in Python?


103
s = [1,2,3,4,5,6,7,8,9]
n = 3

zip(*[iter(s)]*n) # returns [(1,2,3),(4,5,6),(7,8,9)]

Come zip(*[iter(s)]*n)funziona? Come sarebbe se fosse scritto con un codice più dettagliato?


1
dai un'occhiata anche qui dove viene spiegato come funziona: stackoverflow.com/questions/2202461/…
— Matt Joiner

se le risposte qui non bastano, l'ho scritto
— telliott99

7
Sebbene molto intrigante, questa tecnica deve andare contro il valore di "leggibilità" di base di Python!
— Demis

Risposte:


108

iter()è un iteratore su una sequenza. [x] * nproduce una lista contenente la nquantità di x, cioè una lista di lunghezza n, dove si trova ogni elemento x. *argdecomprime una sequenza in argomenti per una chiamata di funzione. Quindi stai passando lo stesso iteratore 3 volte a zip(), e ogni volta estrae un elemento dall'iteratore.

x = iter([1,2,3,4,5,6,7,8,9])
print zip(x, x, x)

1
Buono a sapersi: quando un iteratore yields (= returns) un oggetto, puoi immaginarlo come "consumato". Quindi la prossima volta che viene chiamato l'iteratore, restituisce l'elemento successivo "non consumato".
— winklerrr

46

Le altre ottime risposte e commenti spiegano bene i ruoli dell'argomento unpacking e zip () .

Come dicono Ignacio e ujukatzel , passi a zip()tre riferimenti allo stesso iteratore e fai zip()3-tuple degli interi, in ordine, da ogni riferimento all'iteratore:

1,2,3,4,5,6,7,8,9  1,2,3,4,5,6,7,8,9  1,2,3,4,5,6,7,8,9
^                    ^                    ^            
      ^                    ^                    ^
            ^                    ^                    ^

E poiché chiedi un esempio di codice più dettagliato:

chunk_size = 3
L = [1,2,3,4,5,6,7,8,9]

# iterate over L in steps of 3
for start in range(0,len(L),chunk_size): # xrange() in 2.x; range() in 3.x
    end = start + chunk_size
    print L[start:end] # three-item chunks

Seguendo i valori di starte end:

[0:3) #[1,2,3]
[3:6) #[4,5,6]
[6:9) #[7,8,9]

FWIW, puoi ottenere lo stesso risultato map()con un argomento iniziale di None:

>>> map(None,*[iter(s)]*3)
[(1, 2, 3), (4, 5, 6), (7, 8, 9)]

Per ulteriori informazioni su zip()e map(): http://muffinresearch.co.uk/archives/2007/10/16/python-transposing-lists-with-map-and-zip/


31

Penso che una cosa che manca in tutte le risposte (probabilmente ovvia per chi ha familiarità con gli iteratori) ma non così ovvia per gli altri è:

Poiché abbiamo lo stesso iteratore, viene consumato e gli elementi rimanenti vengono utilizzati dallo zip. Quindi, se usassimo semplicemente l'elenco e non l'iter, ad es.

l = range(9)
zip(*([l]*3)) # note: not an iter here, the lists are not emptied as we iterate 
# output 
[(0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), (6, 6, 6), (7, 7, 7), (8, 8, 8)]

Utilizzando l'iteratore, visualizza i valori e mantiene solo la disponibilità, quindi per zip una volta consumato 0 è disponibile 1, quindi 2 e così via. Una cosa molto sottile, ma abbastanza intelligente !!!


+1, mi hai salvato! Non posso credere che altre risposte abbiano ignorato questo dettaglio vitale supponendo che tutti lo sappiano. Potete fornire riferimenti a una documentazione che includa queste informazioni?
— Snehasish Karmakar

9

iter(s) restituisce un iteratore per s.

[iter(s)]*n crea una lista di n volte lo stesso iteratore per s.

Quindi, quando lo fa zip(*[iter(s)]*n), estrae un elemento da tutti e tre gli iteratori dall'elenco in ordine. Poiché tutti gli iteratori sono lo stesso oggetto, raggruppa semplicemente l'elenco in blocchi di n.


7
Non "n iteratori della stessa lista", ma "n volte lo stesso oggetto iteratore". Oggetti iteratori diversi non condividono lo stato, anche quando appartengono allo stesso elenco.
— Thomas Wouters

Grazie, corretto. In effetti era quello che stavo "pensando", ma ho scritto qualcos'altro.
— sttwister

6

Un consiglio per usare zip in questo modo. Troncerà l'elenco se la sua lunghezza non è divisibile in modo uniforme. Per aggirare questo problema, puoi utilizzare itertools.izip_longest se puoi accettare i valori di riempimento. Oppure potresti usare qualcosa del genere:

def n_split(iterable, n):
    num_extra = len(iterable) % n
    zipped = zip(*[iter(iterable)] * n)
    return zipped if not num_extra else zipped + [iterable[-num_extra:], ]

Uso:

for ints in n_split(range(1,12), 3):
    print ', '.join([str(i) for i in ints])

stampe:

1, 2, 3
4, 5, 6
7, 8, 9
10, 11

3
Questo è già documentato nelle itertoolsricette: docs.python.org/2/library/itertools.html#recipes grouper . Non c'è bisogno di reinventare la ruota
— jamylak

1

Probabilmente è più facile vedere cosa sta succedendo nell'interprete Python o ipythoncon n = 2:

In [35]: [iter("ABCDEFGH")]*2
Out[35]: [<iterator at 0x6be4128>, <iterator at 0x6be4128>]

Quindi, abbiamo un elenco di due iteratori che puntano allo stesso oggetto iteratore. Ricorda che itersu un oggetto restituisce un oggetto iteratore e in questo scenario, è lo stesso iteratore due volte a causa dello *2zucchero sintattico Python. Anche gli iteratori vengono eseguiti una sola volta.

Inoltre, zipaccetta un numero qualsiasi di iterabili (le sequenze sono iterabili ) e crea una tupla dall'i-esimo elemento di ciascuna delle sequenze di input. Poiché entrambi gli iteratori sono identici nel nostro caso, zip sposta lo stesso iteratore due volte per ogni tupla di output a 2 elementi.

In [41]: help(zip)
Help on built-in function zip in module __builtin__:

zip(...)
    zip(seq1 [, seq2 [...]]) -> [(seq1[0], seq2[0] ...), (...)]

    Return a list of tuples, where each tuple contains the i-th element
    from each of the argument sequences.  The returned list is truncated
    in length to the length of the shortest argument sequence.

L' operatore unpacking ( *) assicura che gli iteratori vengano eseguiti fino all'esaurimento, cosa che in questo caso è fino a quando non c'è abbastanza input per creare una tupla a 2 elementi.

Questo può essere esteso a qualsiasi valore di ne zip(*[iter(s)]*n)funziona come descritto.


Scusa per essere lento. Ma potresti spiegare "lo stesso iteratore due volte a causa dello zucchero sintattico * 2 Python. Anche gli iteratori vengono eseguiti solo una volta." parte per favore? Se è così, come mai il risultato non è [("A", "A") ....]? Grazie.
— Bowen Liu

@BowenLiu *è solo comodità per duplicare un oggetto. Provalo con gli scalari e poi con le liste. Prova anche a print(*zip(*[iter("ABCDEFG")]*2))vs print(*zip(*[iter("ABCDEFG"), iter("ABCDEFG")])). Quindi inizia a ridurre i due in passaggi più piccoli per vedere quali sono gli oggetti iteratori effettivi nelle due istruzioni.
— akhan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.