Warum kann ich in einer Python for-Schleife denselben Namen für Iterator und Sequenz verwenden?

78

Dies ist eher eine konzeptionelle Frage. Ich habe kürzlich in Python einen Code gesehen (der in 2.7 funktioniert hat und möglicherweise auch in 2.5 ausgeführt wurde), in dem eine forSchleife denselben Namen sowohl für die Liste, über die iteriert wurde, als auch für das Element in der Liste verwendete , was mir sowohl als schlechte Praxis als auch als etwas erscheint, das überhaupt nicht funktionieren sollte.

Zum Beispiel:

x = [1,2,3,4,5]
for x in x:
    print x
print x

Ausbeuten:

1
2
3
4
5
5

Jetzt ist es für mich sinnvoll, dass der zuletzt gedruckte Wert der letzte Wert ist, der x aus der Schleife zugewiesen wurde, aber ich verstehe nicht, warum Sie für beide Teile der forSchleife denselben Variablennamen verwenden können und haben es funktioniert wie vorgesehen. Sind sie in verschiedenen Bereichen? Was ist unter der Haube los, damit so etwas funktioniert?

Gustav
quelle
1
Als interessantes Gedankenexperiment: Definieren Sie eine Funktion printAndReturn, die ein Argument aufnimmt, es druckt und is zurückgibt. Dann for i in printAndReturn [1,2,3,4,5] …, wie oft soll [1,2,3,4,5]gedruckt werden?
Joshua Taylor
1
Ein Hinweis zum Geltungsbereich, da ihn sonst niemand direkt erwähnt hat: Python hat ein Scoping auf Funktionsebene, aber nichts wie das Scoping auf Blockebene von C. Das Innere und das Äußere der forSchleife haben also den gleichen Umfang.
Izkata
Ich habe den Titel der Frage korrigiert, da dies etwas irreführend war. Nur weil etwas schlecht ist, heißt das nicht, dass es nicht funktioniert. Es kann nur sein, dass es anfälliger für Fehler ist oder schwer zu lesen / zu warten usw.
Nico
Vielen Dank. Ich stimme vollkommen zu, dass es ein schlechter Titel war, ich wusste einfach nicht, wie ich ihn anfangs nennen sollte.
Gustav
Dies funktioniert auch in PHP for ($x as $x), ist aber hässlicher Code IMO
ChiliNUT

Antworten:

67

Was dissagt uns:

Python 3.4.1 (default, May 19 2014, 13:10:29)
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from dis import dis
>>> dis("""x = [1,2,3,4,5]
... for x in x:
...     print(x)
... print(x)""")

  1           0 LOAD_CONST               0 (1)
              3 LOAD_CONST               1 (2)
              6 LOAD_CONST               2 (3)
              9 LOAD_CONST               3 (4)
             12 LOAD_CONST               4 (5)
             15 BUILD_LIST               5
             18 STORE_NAME               0 (x)

  2          21 SETUP_LOOP              24 (to 48)
             24 LOAD_NAME                0 (x)
             27 GET_ITER
        >>   28 FOR_ITER                16 (to 47)
             31 STORE_NAME               0 (x)

  3          34 LOAD_NAME                1 (print)
             37 LOAD_NAME                0 (x)
             40 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             43 POP_TOP
             44 JUMP_ABSOLUTE           28
        >>   47 POP_BLOCK

  4     >>   48 LOAD_NAME                1 (print)
             51 LOAD_NAME                0 (x)
             54 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             57 POP_TOP
             58 LOAD_CONST               5 (None)
             61 RETURN_VALUE

Die Schlüsselbits sind die Abschnitte 2 und 3 - wir laden den Wert aus x( 24 LOAD_NAME 0 (x)) und erhalten dann seinen Iterator ( 27 GET_ITER) und beginnen, darüber zu iterieren ( 28 FOR_ITER). Python kehrt nie wieder zurück, um den Iterator erneut zu laden .

Abgesehen: Es würde keinen Sinn machen , dies zu tun, da sie bereits den Iterator hat, und wie Abhijit in seiner Antwort weist darauf hin , Abschnitt 7.3 der Python - Spezifikation erfordert tatsächlich dieses Verhalten).

Wenn der Name xüberschrieben wird, um auf jeden Wert in der Liste zu verweisen, der früher als xPython bekannt war, hat er keine Probleme, den Iterator zu finden, da er den Namen nie wieder überprüfen muss, um xdas Iterationsprotokoll zu beenden.

Sean Vieira
quelle
8
"Python kehrt nie wieder zurück, um den Iterator erneut zu laden (dies wäre nicht sinnvoll, da der Iterator bereits vorhanden ist)." Dies beschreibt das Verhalten , dass Sie in der Demontage zu beobachten, aber nicht sagen , ob das hat der Fall sein oder nicht; Abhijits Antwort zitiert das Handbuch, in dem dies tatsächlich angegeben ist.
Joshua Taylor
42

Verwenden Sie Ihren Beispielcode als Kernreferenz

x = [1,2,3,4,5]
for x in x:
    print x
print x

Ich möchte, dass Sie auf Abschnitt 7.3 verweisen . Die for-Anweisung im Handbuch

Auszug 1

Die Ausdrucksliste wird einmal ausgewertet. es sollte ein iterierbares Objekt ergeben. Für das Ergebnis der expression_list wird ein Iterator erstellt.

Was es bedeutet, dass Ihre Variable x, die ein symbolischer Name eines Objekts ist list: [1,2,3,4,5]zu einem iterable Objekt ausgewertet wird. Selbst wenn die Variable, die symbolische Referenz, ihre Zugehörigkeit ändert, da die Ausdrucksliste nicht erneut ausgewertet wird, hat dies keine Auswirkungen auf das iterierbare Objekt, das bereits ausgewertet und generiert wurde.

Hinweis

  • Alles in Python ist ein Objekt, hat einen Bezeichner, Attribute und Methoden.
  • Variablen sind symbolische Namen, Verweise auf ein und nur ein Objekt in einer bestimmten Instanz.
  • Variablen zur Laufzeit können ihre Zugehörigkeit ändern, dh auf ein anderes Objekt verweisen.

Auszug 2

Die Suite wird dann einmal für jedes vom Iterator bereitgestellte Element in der Reihenfolge aufsteigender Indizes ausgeführt.

Hier bezieht sich die Suite auf den Iterator und nicht auf die Ausdrucksliste. Daher wird der Iterator für jede Iteration ausgeführt, um das nächste Element zu erhalten, anstatt auf die ursprüngliche Ausdrucksliste zu verweisen.

Abhijit
quelle
5

Es ist notwendig, dass es so funktioniert, wenn Sie darüber nachdenken. Der Ausdruck für die Sequenz einer forSchleife kann alles sein:

binaryfile = open("file", "rb")
for byte in binaryfile.read(5):
    ...

Wir können die Sequenz nicht bei jedem Durchlauf durch die Schleife abfragen, oder hier würden wir beim zweiten Mal aus dem nächsten Stapel von 5 Bytes lesen . Natürlich muss Python das Ergebnis des Ausdrucks in irgendeiner Weise privat speichern, bevor die Schleife beginnt.


Sind sie in verschiedenen Bereichen?

Nein. Um dies zu bestätigen, können Sie einen Verweis auf das ursprüngliche Bereichswörterbuch ( local () ) behalten und feststellen, dass Sie tatsächlich dieselben Variablen in der Schleife verwenden:

x = [1,2,3,4,5]
loc = locals()
for x in x:
    print locals() is loc  # True
    print loc["x"]  # 1
    break

Was ist unter der Haube los, damit so etwas funktioniert?

Sean Vieira hat genau gezeigt, was unter der Haube vor sich geht, aber um es in besser lesbarem Python-Code zu beschreiben, entspricht Ihre forSchleife im Wesentlichen dieser whileSchleife:

it = iter(x)
while True:
    try:
        x = it.next()
    except StopIteration:
        break
    print x

Dies unterscheidet sich von dem herkömmlichen Indizierungsansatz für die Iteration, den Sie in älteren Java-Versionen sehen würden, zum Beispiel:

for (int index = 0; index < x.length; index++) {
    x = x[index];
    ...
 }

Dieser Ansatz schlägt fehl, wenn die Elementvariable und die Sequenzvariable identisch sind, da die Sequenz xnicht mehr verfügbar ist, um den nächsten Index nachzuschlagen, nachdem das erste Mal xdem ersten Element zugewiesen wurde.

Beim ersteren Ansatz it = iter(x)fordert die erste Zeile ( ) jedoch ein Iteratorobjekt an, das tatsächlich für die Bereitstellung des nächsten Elements von da an verantwortlich ist. Auf die Sequenz, auf die xursprünglich verwiesen wurde, muss nicht mehr direkt zugegriffen werden.

nmclean
quelle
4

Es ist der Unterschied zwischen einer Variablen (x) und dem Objekt, auf das sie zeigt (die Liste). Wenn die for-Schleife startet, greift Python auf einen internen Verweis auf das Objekt zu, auf das x zeigt. Es verwendet das Objekt und nicht das, worauf x zu einem bestimmten Zeitpunkt verweist.

Wenn Sie x neu zuweisen, ändert sich die for-Schleife nicht. Wenn x auf ein veränderbares Objekt (z. B. eine Liste) zeigt und Sie dieses Objekt ändern (z. B. ein Element löschen), können die Ergebnisse unvorhersehbar sein.

tdelaney
quelle
3

Grundsätzlich nimmt die for - Schleife in der Liste x, und dann, dass die Speicherung als temporärer Variable, erneut Weist eine xan jeden Wert in diesen temporären Variablen. Somit xist jetzt der letzte Wert in der Liste.

>>> x = [1, 2, 3]
>>> [x for x in x]
[1, 2, 3]
>>> x
3
>>> 

Genau wie in diesem:

>>> def foo(bar):
...     return bar
... 
>>> x = [1, 2, 3]
>>> for x in foo(x):
...     print x
... 
1
2
3
>>> 

In diesem Beispiel xwird gespeichert foo()als bar, so dass , obwohl xneu zugewiesen wird, es gibt sie noch (ED) in foo()so dass wir es unsere auszulösen verwenden könnten forSchleife.

ZenOfPython
quelle
Eigentlich glaube ich im letzten Beispiel nicht, dass xes neu zugewiesen wird. Eine lokale Variable barwird in erstellt foound der Wert von zugewiesen x. foogibt dann diesen Wert in Form eines Objekts zurück, das in der forBedingung verwendet wird. Daher wurde die Variable xim zweiten Beispiel nie neu zugewiesen. Ich stimme dem ersten jedoch zu.
Tonio
@Tonio xist jedoch immer noch die Iterationsvariable und nimmt daher für jede Schleife einen neuen Wert an. Nach der Schleife xist 3in beiden Fällen gleich.
Peter Gibson
@ PeterGibson Du hast absolut Recht, es ist mir entgangen.
Tonio
Wenn es eine "neue Variable" innerhalb der Schleife wäre, wie kommt es dann, wenn die Schleife xgilt 3und not [1,2,3] `?
Joshua Taylor
@JoshuaTaylor In Python ist die Schleifenindexvariable lexikalisch auf den Block beschränkt, in dem die for-Schleife aufgetreten ist.
HennyH
1

xbezieht sich nicht mehr auf die ursprüngliche xListe, daher gibt es keine Verwirrung. Grundsätzlich merkt sich Python, dass es über die ursprüngliche xListe iteriert , aber sobald Sie anfangen, dem Namen den Iterationswert (0,1,2 usw.) zuzuweisen, xverweist es nicht mehr auf die ursprüngliche xListe. Der Name wird dem Iterationswert neu zugewiesen.

In [1]: x = range(5)

In [2]: x
Out[2]: [0, 1, 2, 3, 4]

In [3]: id(x)
Out[3]: 4371091680

In [4]: for x in x:
   ...:     print id(x), x
   ...:     
140470424504688 0
140470424504664 1
140470424504640 2
140470424504616 3
140470424504592 4

In [5]: id(x)
Out[5]: 140470424504592
Noah
quelle
2
Es wird nicht so sehr eine Kopie der Bereichsliste erstellt (da Änderungen an der Liste immer noch zu undefiniertem Verhalten in der Iteration führen würden). xhört einfach auf, auf die Bereichsliste zu verweisen, und erhält stattdessen die neuen Iterationswerte. Die Bereichsliste ist noch intakt. Wenn Sie sich den Wert von xnach der Schleife ansehen , wird es sein4
Peter Gibson
"x bezieht sich nicht mehr auf das ursprüngliche x", auf das xnie Bezug genommen wurde x; xauf eine Sequenz bezogen. Dann bezog es sich auf 1, dann auf 2usw.
Joshua Taylor