web-dev-qa-db-fra.com

Pourquoi puis-je utiliser le même nom pour l'itérateur et la séquence dans une boucle Python pour?

Il s'agit davantage d'une question conceptuelle. J'ai récemment vu un morceau de code en Python (cela fonctionnait en 2.7, et il aurait aussi pu être exécuté en 2.5)) dans lequel une boucle for utilisait le même nom à la fois pour la liste en cours d'itération et pour l'élément de la liste, ce qui me semble à la fois une mauvaise pratique et quelque chose qui ne devrait pas fonctionner du tout.

Par exemple:

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

Rendements:

1
2
3
4
5
5

Maintenant, il est logique pour moi que la dernière valeur imprimée soit la dernière valeur affectée à x de la boucle, mais je n'arrive pas à comprendre pourquoi vous seriez en mesure d'utiliser le même nom de variable pour vos deux parties de for boucle et faire fonctionner comme prévu. Sont-ils dans des portées différentes? Que se passe-t-il sous le capot qui permet à quelque chose comme ça de fonctionner?

78
Gustav

Que nous dit dis:

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

Les bits clés sont les sections 2 et 3 - nous chargeons la valeur de x (24 LOAD_NAME 0 (x)) puis nous obtenons son itérateur (27 GET_ITER) Et commençons à itérer dessus ( 28 FOR_ITER). Python ne revient jamais pour charger à nouveau l'itérateur .

À part: Cela n'aurait aucun sens, car il a déjà l'itérateur, et comme Abhijit souligne dans sa réponse , Section 7.3 des spécifications de Python nécessite en fait ce comportement).

Lorsque le nom x est écrasé pour pointer sur chaque valeur de la liste anciennement connue sous le nom de x Python n'a aucun problème à trouver l'itérateur car il n'a plus jamais besoin de revoir le nom x pour terminer le protocole d'itération.

67
Sean Vieira

Utiliser votre exemple de code comme référence principale

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

Je voudrais que vous vous référiez à la section 7.3. L'instruction for dans le manuel

Extrait 1

La liste d'expressions est évaluée une fois; il devrait produire un objet itérable. Un itérateur est créé pour le résultat de la liste d'expressions.

Cela signifie que votre variable x, qui est le nom symbolique d'un objet list: [1,2,3,4,5] est évalué en un objet itérable. Même si la variable, la référence symbolique change son allégeance, comme expression-list n'est pas évalué à nouveau, il n'y a aucun impact sur l'objet itérable qui a déjà été évalué et généré.

Remarque

  • Tout dans Python est un objet, a un identifiant, des attributs et des méthodes.
  • Les variables sont un nom symbolique, une référence à un et un seul objet à une instance donnée.
  • Les variables au moment de l'exécution peuvent changer son allégeance, c'est-à-dire peuvent se référer à un autre objet.

Extrait 2

La suite est ensuite exécutée une fois pour chaque élément fourni par l'itérateur, dans l'ordre des indices ascendants.

Ici, la suite fait référence à l'itérateur et non à la liste d'expressions. Ainsi, pour chaque itération, l'itérateur est exécuté pour produire l'élément suivant au lieu de se référer à la liste d'expressions d'origine.

42
Abhijit

Il faut qu'il fonctionne de cette façon, si vous y réfléchissez. L'expression de la séquence d'une boucle for peut être n'importe quoi:

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

Nous ne pouvons pas interroger la séquence à chaque passage dans la boucle, ou ici nous finirions par lire le prochain lot de 5 octets la deuxième fois . Naturellement Python doit en quelque sorte stocker le résultat de l'expression en privé avant le début de la boucle.


Sont-ils dans des portées différentes?

Non. Pour confirmer cela, vous pouvez conserver une référence au dictionnaire de portée d'origine ( locals () ) et remarquer que vous utilisez en fait les mêmes variables à l'intérieur de la boucle:

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

Que se passe-t-il sous le capot qui permet à quelque chose comme ça de fonctionner?

Sean Vieira a montré exactement ce qui se passe sous le capot, mais pour le décrire dans un code plus lisible python, votre boucle for est essentiellement équivalente à cette boucle while:

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

Ceci est différent de l'approche d'indexation traditionnelle de l'itération que vous verriez dans les anciennes versions de Java, par exemple:

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

Cette approche échouerait lorsque la variable d'élément et la variable de séquence sont identiques, car la séquence x ne serait plus disponible pour rechercher l'index suivant après la première réaffectation de x à la premier élément.

Avec la première approche, cependant, la première ligne (it = iter(x)) demande un objet itérateur qui est en fait responsable de fournir l'élément suivant à partir de ce moment. Il n'est plus nécessaire d'accéder directement à la séquence que x indiquait à l'origine.

5
nmclean

C'est la différence entre une variable (x) et l'objet vers lequel elle pointe (la liste). Lorsque la boucle for démarre, Python saisit une référence interne à l'objet pointé par x. Il utilise l'objet et non ce que x arrive à référencer à un moment donné.

Si vous réaffectez x, la boucle for ne change pas. Si x pointe vers un objet modifiable (par exemple, une liste) et que vous modifiez cet objet (par exemple, supprimez un élément), les résultats peuvent être imprévisibles.

4
tdelaney

Fondamentalement, la boucle for prend la liste x, puis, en la stockant en tant que variable temporaire, re affecte un x à chaque valeur de cette variable temporaire. Ainsi, x est désormais la dernière valeur de la liste.

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

Tout comme dans ceci:

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

Dans cet exemple, x est stocké dans foo() sous la forme bar, donc bien que x soit en cours de réaffectation, il existe toujours (ed) dans foo() afin que nous puissions l'utiliser pour déclencher notre boucle for.

3
ZenOfPython

x ne fait plus référence à la liste x d'origine, il n'y a donc pas de confusion. Fondamentalement, python se souvient qu'il itère sur la liste d'origine x, mais dès que vous commencez à affecter la valeur d'itération (0,1,2, etc.) au nom x, il ne fait plus référence à la liste d'origine x. Le nom est réaffecté à la valeur d'itération.

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
1
Noah