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.