Wie funktioniert zip (* [iter (s)] * n) in Python?

102
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)]

Wie funktioniert das zip(*[iter(s)]*n)? Wie würde es aussehen, wenn es mit ausführlicherem Code geschrieben würde?

Oliver Zheng
quelle
1
Schauen Sie auch hier, wo es auch erklärt wird: stackoverflow.com/questions/2202461/…
Matt Joiner
Wenn die Antworten hier nicht ausreichen, habe ich sie hier gebloggt
telliott99
7
Obwohl diese Technik sehr faszinierend ist, muss sie gegen den Kernwert "Lesbarkeit" von Python verstoßen!
Demis

Antworten:

108

iter()ist ein Iterator über eine Sequenz. [x] * nerzeugt eine Liste mit einer nMenge von x, dh einer Liste der Länge n, in der sich jedes Element befindet x. *argentpackt eine Sequenz in Argumente für einen Funktionsaufruf. Daher übergeben Sie denselben Iterator dreimal an zip()und er zieht jedes Mal ein Element aus dem Iterator.

x = iter([1,2,3,4,5,6,7,8,9])
print zip(x, x, x)
Ignacio Vazquez-Abrams
quelle
1
Gut zu wissen: Wenn ein Iterator ein Element yield(= returns) ist, können Sie sich dieses Element als "verbraucht" vorstellen. Wenn der Iterator das nächste Mal aufgerufen wird, ergibt er das nächste "nicht verbrauchte" Element.
Winklerrr
46

Die anderen tollen Antworten und Kommentare erklären gut die Rolle des Auspackens von Argumenten und von zip () .

Wie Ignacio und Ujukatzel sagen, gehen Sie zu zip()drei Verweisen auf denselben Iterator über und erstellenzip() aus jedem Verweis auf den Iterator 3-Tupel der Ganzzahlen - in der Reihenfolge -:

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
^                    ^                    ^            
      ^                    ^                    ^
            ^                    ^                    ^

Und da Sie nach einem ausführlicheren Codebeispiel fragen:

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

Folgen Sie den Werten von startund end:

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

FWIW, Sie können das gleiche Ergebnis map()mit einem anfänglichen Argument von erhalten None:

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

Weitere Informationen zu zip()und map(): http://muffinresearch.co.uk/archives/2007/10/16/python-transposing-lists-with-map-and-zip/

mechanisches Fleisch
quelle
31

Ich denke, eine Sache, die in allen Antworten übersehen wird (wahrscheinlich offensichtlich für diejenigen, die mit Iteratoren vertraut sind), aber für andere nicht so offensichtlich ist:

Da wir denselben Iterator haben, wird er verbraucht und die restlichen Elemente werden von der Zip-Datei verwendet. Wenn wir also einfach die Liste und nicht den Iter verwenden, z.

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)]

Wenn Sie den Iterator verwenden, werden die Werte eingeblendet und bleiben nur verfügbar, sodass für zip, sobald 0 verbraucht ist, 1 verfügbar ist und dann 2 und so weiter. Eine sehr subtile Sache, aber ziemlich klug !!!

gabhijit
quelle
+1, Du hast mich gerettet! Ich kann nicht glauben, dass andere Antworten dieses wichtige Detail übersprungen haben, vorausgesetzt, jeder weiß das. Können Sie auf eine Dokumentation verweisen, die diese Informationen enthält?
Snehasish Karmakar
9

iter(s) Gibt einen Iterator für s zurück.

[iter(s)]*n macht eine Liste von n mal dem gleichen Iterator für s.

Dabei zip(*[iter(s)]*n)wird ein Element aus allen drei Iteratoren der Reihe nach aus der Liste extrahiert. Da alle Iteratoren dasselbe Objekt sind, wird die Liste nur in Blöcke von gruppiert n.

sttwister
quelle
7
Nicht 'n Iteratoren derselben Liste', sondern 'n mal dasselbe Iteratorobjekt'. Verschiedene Iteratorobjekte haben keinen gemeinsamen Status, selbst wenn sie zur selben Liste gehören.
Thomas Wouters
Danke, korrigiert. Das habe ich zwar "gedacht", aber etwas anderes geschrieben.
Sttwister
6

Ein Ratschlag für die Verwendung von zip auf diese Weise. Ihre Liste wird abgeschnitten, wenn ihre Länge nicht gleichmäßig teilbar ist. Um dies zu umgehen , können Sie entweder itertools.izip_longest verwenden, wenn Sie Füllwerte akzeptieren können. Oder Sie könnten so etwas verwenden:

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:], ]

Verwendung:

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

Drucke:

1, 2, 3
4, 5, 6
7, 8, 9
10, 11
jmagnusson
quelle
3
Dies ist bereits in itertoolsRezepten dokumentiert : docs.python.org/2/library/itertools.html#recipes grouper . Keine Notwendigkeit, das Rad neu zu erfinden
Jamylak
1

Es ist wahrscheinlich einfacher zu sehen, was im Python-Interpreter passiert oder ipythonmit n = 2:

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

Wir haben also eine Liste von zwei Iteratoren, die auf dasselbe Iteratorobjekt zeigen. Denken Sie daran, dass iterein Objekt ein Iteratorobjekt zurückgibt und in diesem Szenario aufgrund des *2syntaktischen Python-Zuckers zweimal derselbe Iterator ist . Iteratoren laufen auch nur einmal.

Ferner zipnimmt eine beliebige Anzahl von Iterables ( Sequenzen sind Iterables ) und erstellt Tupel aus i'th Elemente von jedem der Eingangssequenzen. Da in unserem Fall beide Iteratoren identisch sind, verschiebt zip denselben Iterator zweimal für jedes 2-Element-Tupel der Ausgabe.

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.

Der Operator unpacking ( *) stellt sicher, dass die Iteratoren bis zur Erschöpfung ausgeführt werden. In diesem Fall ist dies so lange der Fall, bis nicht mehr genügend Eingaben vorhanden sind, um ein Tupel mit zwei Elementen zu erstellen.

Dies kann auf jeden Wert von erweitert werden nund zip(*[iter(s)]*n)funktioniert wie beschrieben.

Akhan
quelle
Tut mir leid, dass ich langsam bin. Aber könnten Sie den "gleichen Iterator zweimal aufgrund des syntaktischen * 2-Python-Zuckers erklären? Iteratoren werden auch nur einmal ausgeführt." Teil bitte? Wenn ja, warum ist das Ergebnis nicht [("A", "A") ....]? Vielen Dank.
Bowen Liu
@BowenLiu *ist nur eine bequeme Möglichkeit, ein Objekt zu duplizieren. Versuchen Sie es mit Skalaren und dann mit Listen. Versuchen Sie auch print(*zip(*[iter("ABCDEFG")]*2))vs print(*zip(*[iter("ABCDEFG"), iter("ABCDEFG")])). Zerreißen Sie dann die beiden in kleinere Schritte, um zu sehen, was die tatsächlichen Iteratorobjekte in den beiden Anweisungen sind.
Akhan