Warum funktioniert x, y = zip (* zip (a, b)) in Python?

78

OK, ich liebe Pythons zip()Funktion. Verwenden Sie es die ganze Zeit, es ist brillant. Hin und wieder möchte ich das Gegenteil von zip()"Ich habe früher gewusst, wie man das macht" machen, dann Google Python entpacken und dann daran denken, dass man diese *Magie verwendet, um eine komprimierte Liste von Tupeln zu entpacken. So was:

x = [1,2,3]
y = [4,5,6]
zipped = zip(x,y)
unzipped_x, unzipped_y = zip(*zipped)
unzipped_x
    Out[30]: (1, 2, 3)
unzipped_y
    Out[31]: (4, 5, 6)

Was zu Hölle ist hier los? Was macht dieses magische Sternchen? Wo sonst kann es angewendet werden und welche anderen erstaunlichen Dinge in Python sind so mysteriös und schwer zu googeln?

Mike Dewar
quelle
3
Oh ja. Dies ist jedoch genau das Problem. Wenn Sie den Stackoverflow nach zip(*Python durchsuchen, wird die doppelte Frage auf der ersten Seite nicht zurückgegeben, und wenn Sie googeln python *oder python zip(*nicht viel zurückgeben, denke ich, weil das (*ignoriert wird. Du hast aber recht, jemand anderes fand das auch großartig. Soll ich die Frage löschen?
Mike Dewar
1
Ich würde es nicht löschen, da es bei der Suche aus irgendeinem Grund einen höheren Rang einnimmt. Wenn Sie es schließen, kann es als Weiterleitung dienen.
Josh Lee
5
Ich habe den in meiner Antwort angegebenen Link gefunden, indem ich nach "site: docs.python.org asterisk" gesucht habe. Das Wort "Sternchen" ist für Suchmaschinen viel einfacher als ein tatsächliches Sternchen. :-)
Daniel Stutzbach
4
"Welche anderen erstaunlichen fantastischen Dinge in Python sind so mysteriös und schwer zu googeln?" Überprüfen Sie heraus: stackoverflow.com/questions/101268/hidden-features-of-python für die Antwort :)
Adam Parkin

Antworten:

18

Das Sternchen wird ausgeführt apply(wie es in Lisp und Schema bekannt ist). Grundsätzlich nimmt es Ihre Liste und ruft die Funktion mit dem Inhalt dieser Liste als Argumente auf.

Chris Jester-Young
quelle
1
Die Python2-Serie hat noch eine applyFunktion, aber ich glaube nicht, dass es Anwendungsfälle gibt, die nicht abgedeckt werden können *. Ich glaube, es wurde aus Python3 entfernt
John La Rooy
1
@gnibbler: Bestätigt. applyist unter python.org/dev/peps/pep-0361 unter der ÜberschriftWarnings for features removed in Py3k:
MatrixFrog
2
Übernehmen ist nur vorhanden, weil das Sternchen später hinzugefügt wurde.
DasIch
9

Es ist auch nützlich für mehrere Argumente:

def foo(*args):
  print args

foo(1, 2, 3) # (1, 2, 3)

# also legal
t = (1, 2, 3)
foo(*t) # (1, 2, 3)

Außerdem können Sie für Schlüsselwortargumente und Wörterbücher ein doppeltes Sternchen verwenden:

def foo(**kwargs):
   print kwargs

foo(a=1, b=2) # {'a': 1, 'b': 2}

# also legal
d = {"a": 1, "b": 2}
foo(**d) # {'a': 1, 'b': 2}

Und natürlich können Sie diese kombinieren:

def foo(*args, **kwargs):
   print args, kwargs

foo(1, 2, a=3, b=4) # (1, 2) {'a': 3, 'b': 4}

Ziemlich ordentliches und nützliches Zeug.

Bcherry
quelle
6

Es funktioniert nicht immer:

>>> x = []
>>> y = []
>>> zipped = zip(x, y)
>>> unzipped_x, unzipped_y = zip(*zipped)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: need more than 0 values to unpack

Hoppla! Ich denke, es braucht einen Schädel, um es zum Arbeiten zu bringen:

>>> unzipped_x, unzipped_y = zip(*zipped) or ([], [])
>>> unzipped_x
[]
>>> unzipped_y
[]

In Python3 denke ich, dass Sie brauchen

>>> unzipped_x, unzipped_y = tuple(zip(*zipped)) or ([], [])

da zip jetzt eine Generatorfunktion zurückgibt, die nicht False-y ist.

BenAnhalt
quelle
Oder verwenden Sie einfach einen Generator unzipped_x = (z[0] for z in zipped). Wenn zippedes sich selbst um einen Generator handelt, konvertieren Sie ihn zuerst in eine Liste, damit Sie ihn erneut durchlaufen können unzipped_y. Es fallen keine zusätzlichen Kosten an, zip(*zipped)da letztere zippedbeim Entpacken der Argumente auch in eine Liste konvertiert werden .
Ian Goldby
2

Ich bin sehr neu in Python, daher hat mich das erst kürzlich gestolpert, aber es hatte mehr damit zu tun, wie das Beispiel präsentiert und was hervorgehoben wurde.

Was mir Probleme beim Verständnis des Zip-Beispiels bereitete, war die Asymmetrie bei der Behandlung der Rückgabewerte des Zip-Anrufs. Das heißt, wenn zip zum ersten Mal aufgerufen wird, wird der Rückgabewert einer einzelnen Variablen zugewiesen, wodurch eine Listenreferenz erstellt wird (die die erstellte Tupelliste enthält). Beim zweiten Aufruf wird die Fähigkeit von Python genutzt, einen Listen- (oder Sammlungs-?) Rückgabewert automatisch in mehrere Variablenreferenzen zu entpacken, wobei jede Referenz das einzelne Tupel ist. Wenn jemand nicht weiß, wie das in Python funktioniert, kann er sich leichter darüber verirren, was tatsächlich passiert.

>>> x = [1, 2, 3]
>>> y = "abc"
>>> zipped = zip(x, y)
>>> zipped
[(1, 'a'), (2, 'b'), (3, 'c')]
>>> z1, z2, z3 = zip(x, y)
>>> z1
(1, 'a')
>>> z2
(2, 'b')
>>> z3
(3, 'c')
>>> rezipped = zip(*zipped)
>>> rezipped
[(1, 2, 3), ('a', 'b', 'c')]
>>> rezipped2 = zip(z1, z2, z3)
>>> rezipped == rezipped2
True
user3447701
quelle
0

Nachtrag zu @ bcherrys Antwort:

>>> def f(a2,a1):
...  print a2, a1
... 
>>> d = {'a1': 111, 'a2': 222}
>>> f(**d)
222 111

Es funktioniert also nicht nur mit Schlüsselwortargumenten (in diesem strengen Sinne ), sondern auch mit benannten Argumenten (auch als Positionsargumente bezeichnet ).

Evgeni Sergeev
quelle
0

(x, y) == tuple(zip(*zip(x,y))) ist genau dann wahr, wenn die beiden folgenden Aussagen wahr sind:

  • xund yhaben die gleiche Länge
  • xund ysind Tupel

Ein guter Weg, um zu verstehen, was los ist, ist das Drucken bei jedem Schritt:

x = [1, 2, 3]
y = ["a", "b", "c", "d"]

print("1) x, y = ", x, y)
print("2) zip(x, y) = ", list(zip(x, y)))
print("3) *zip(x, y) = ", *zip(x, y))
print("4) zip(*zip(x,y)) = ", list(zip(*zip(x,y))))

Welche Ausgänge:

1) x, y =            [1, 2, 3] ['a', 'b', 'c', 'd']
2) zip(x, y) =       [(1, 'a'), (2, 'b'), (3, 'c')]
3) *zip(x, y) =       (1, 'a')  (2, 'b')  (3, 'c')
4) zip(*zip(x,y)) =  [(1, 2, 3), ('a', 'b', 'c')]

Grundsätzlich passiert Folgendes:

  1. Elemente aus xund ywerden entsprechend ihren jeweiligen Indizes gepaart.
  2. Paare werden zu 3 verschiedenen Objekten (Tupeln) ausgepackt
  3. Paare werden an zip übergeben, wodurch wiederum alle Elemente anhand von Indizes gepaart werden:
    • Die ersten Elemente aller Eingaben werden gepaart: (1, 2, 3)
    • zweite Elemente von allen Eingaben werden gepaart: ('a', 'b', 'c')

Jetzt können Sie verstehen, warum (x, y) == tuple(zip(*zip(x,y)))in diesem Fall falsch ist:

  • Da yes länger als ist x, hat der erste Zip-Vorgang das zusätzliche Element entfernt y(da es nicht gekoppelt werden konnte), wird diese Änderung offensichtlich beim zweiten Zip-Vorgang erneut ausgeführt
  • Typen unterscheiden sich, am Anfang hatten wir zwei Listen, jetzt haben wir zwei Tupel, ebenso zipwie Paarelemente in Tupeln und nicht in Listen

Wenn Sie nicht 100% sicher sind, wie es zipfunktioniert, habe ich hier eine Antwort auf diese Frage geschrieben: Entpacken und der Operator *

cglacet
quelle