2014-01-10 3 views
18
In [55]: a = 5 

In [56]: b = 6 

In [57]: (a, b) = (b, a) 

In [58]: a 
Out[58]: 6 

In [59]: b 
Out[59]: 5 

Wie funktioniert dieser Austausch von Werten von a und b intern Arbeit? Es verwendet definitiv keine Temp-Variable.Wie funktioniert das Tauschen von Membern in den Python-Tupeln (a, b) = (b, a) intern?

+0

'Seine Verwendung auf keinen Fall eine temporäre Variable?' Dies eine seltsame Frage. Es klingt, als ob du es weißt. – RedX

+1

Es könnte für Sie interessant sein, die Disassemblierung Ihres Codes mit ['dis'] (http://docs.python.org/3/library/dis.html) anzuzeigen. Spoliers: der Bytecode-Befehl [ 'ROT_TWO'] (http://docs.python.org/2/library/dis.html#opcode-ROT_TWO) verwendet wird. – Kevin

+0

@RedX: Ich hätte diese Frage nicht gestellt, wenn ich es gewusst hätte. Die Verwendung einer temporären Variable ist ein trivialer Ansatz zum Auslagern. – praveen

Antwort

54

Python trennt die rechte Seite Ausdruck aus der linken Seite-Zuordnung. Zuerst wird die rechte Seite wird ausgewertet und das Ergebnis auf dem Stapel gespeichert wird, und dann die linke Seite Namen verwenden Opcodes zugeordnet, die Werte von der Stapel wieder nehmen.

Für Tupel-Zuordnungen mit 2 oder 3 Elemente, Python verwendet nur den Stapel direkt:

>>> import dis 
>>> def foo(a, b): 
...  a, b = b, a 
... 
>>> dis.dis(foo) 
    2   0 LOAD_FAST    1 (b) 
       3 LOAD_FAST    0 (a) 
       6 ROT_TWO    
       7 STORE_FAST    0 (a) 
      10 STORE_FAST    1 (b) 
      13 LOAD_CONST    0 (None) 
      16 RETURN_VALUE   

Nach der zwei LOAD_FAST opcodes (die einen Wert von einer Variablen auf den Stapel schieben), hält die Oberseite des Stapels [a, b] . Der ROT_TWO opcode tauscht die obersten zwei Positionen auf dem Stapel, so dass der Stapel jetzt [b, a] an der Spitze hat. Die beiden STORE_FAST opcodes nehmen dann diese beiden Werte und speichern sie in den Namen auf der linken Seite der Zuweisung. Die erste STORE_FAST erscheint einen Wert der oben auf dem Stapel und legt es in a, wieder am nächsten Pop, den Wert in b speichern. Die Rotation wird benötigt, da Python garantiert, dass Zuordnungen in einer Zielliste auf der linken Seite von links nach rechts vorgenommen werden.

Für eine 3-Name-Zuweisung wird ROT_THREE gefolgt von ROT_TWO ausgeführt, um die obersten drei Elemente auf dem Stapel umzukehren.

Für längere linke seitige Aufgaben wird eine explizite Tupel gebaut:

>>> def bar(a, b, c, d): 
...  d, c, b, a = a, b, c, d 
... 
>>> dis.dis(bar) 
    2   0 LOAD_FAST    0 (a) 
       3 LOAD_FAST    1 (b) 
       6 LOAD_FAST    2 (c) 
       9 LOAD_FAST    3 (d) 
      12 BUILD_TUPLE    4 
      15 UNPACK_SEQUENCE   4 
      18 STORE_FAST    3 (d) 
      21 STORE_FAST    2 (c) 
      24 STORE_FAST    1 (b) 
      27 STORE_FAST    0 (a) 
      30 LOAD_CONST    0 (None) 
      33 RETURN_VALUE   

Hier wird der Stapel mit [d, c, b, a] wird verwendet, um ein Tupel zu bauen (in umgekehrter Reihenfolge, BUILD_TUPLE Pops aus dem Stapel wieder, Schieben das resultierende Tupel auf den Stack), und dann UNPACK_SEQUENCE das Tupel wieder vom Stapel löscht, schiebt alle Elemente zurück aus dem Tupel zurück auf den Stack für die STORE_FAST Operationen.

Letztere wie ein verschwenderischer Betrieb zu sein scheint, aber die rechte Seite einer Zuweisung kann etwas ganz anderes, ein Funktionsaufruf sein, dass ein Tupel erzeugt vielleicht, so dass der Python-Interpreter keine Annahmen macht und nutzt die UNPACK_SEQUENCE Opcode immer. Es tut dies sogar für die zwei- und drei-Namen-Zuweisungsoperationen, but a later (peephole) optimization step ersetzt eine BUILD_TUPLE/UNPACK_SEQUENCE Kombination mit 2 oder 3 Argumenten mit den obigen ROT_TWO und ROT_THREE Opcodes für die Effizienz.

+1

+1 für ** Die Rotation wird benötigt, da Python garantiert, dass Zuweisungen in einer Zielliste auf der linken Seite von links nach rechts erfolgen. **. Das wusste ich nicht. –

Verwandte Themen