¿Por qué puedo usar el mismo nombre para iterador y secuencia en un bucle de Python for?

Esta es más una pregunta conceptual. Hace poco vi un fragmento de código en Python (funcionaba en 2.7, y también podría haberse ejecutado en 2.5) en el que un bucle for usaba el mismo nombre tanto para la lista que estaba siendo iterada como para el elemento en el lista, que me parece una mala práctica y algo que no debería funcionar en absoluto.

Por ejemplo:

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

Rendimientos:

 1 2 3 4 5 5 

Ahora, tiene sentido para mí que el último valor impreso sea el último valor asignado a x desde el bucle, pero no entiendo por qué sería capaz de usar el mismo nombre de variable para ambas partes del bucle for y haz que funcione como es debido. ¿Están en diferentes ámbitos? ¿Qué está pasando bajo el capó que permite que algo como esto funcione?

¿Qué nos dice 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 

Los bits clave son las secciones 2 y 3: 24 LOAD_NAME 0 (x) el valor de x ( 24 LOAD_NAME 0 (x) ) y luego obtenemos su iterador ( 27 GET_ITER ) y comenzamos a iterar sobre él ( 28 FOR_ITER ). Python nunca vuelve a cargar el iterador de nuevo .

Aparte: No tendría ningún sentido hacerlo, ya que ya tiene el iterador, y como Abhijit señala en su respuesta , la Sección 7.3 de la especificación de Python en realidad requiere este comportamiento).

Cuando el nombre x se sobrescribe para apuntar a cada valor dentro de la lista anteriormente conocida como x Python, no tiene ningún problema para encontrar el iterador, ya que nunca más debe mirar el nombre x para finalizar el protocolo de iteración.

Usando su código de ejemplo como la referencia central

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

Me gustaría que refiera la sección 7.3. La statement en el manual

Extracto 1

La lista de expresiones se evalúa una vez; debe producir un objeto iterable. Se crea un iterador para el resultado de la lista_expresión.

Lo que significa es que su variable x , que es un nombre simbólico de una list objetos: [1,2,3,4,5] se evalúa como un objeto iterable. Incluso si la variable, la referencia simbólica cambia su lealtad, ya que la lista de expresiones no se evalúa nuevamente, no hay impacto en el objeto iterable que ya ha sido evaluado y generado.

Nota

  • Todo en Python es un Objeto, tiene un Identificador, atributos y métodos.
  • Las variables son el nombre simbólico, una referencia a uno y solo un objeto en cualquier instancia dada.
  • Las variables en tiempo de ejecución pueden cambiar su lealtad, es decir, pueden referirse a algún otro objeto.

Extracto 2

La serie se ejecuta una vez para cada elemento proporcionado por el iterador, en el orden de los índices ascendentes.

Aquí la suite se refiere al iterador y no a la lista de expresiones. Entonces, para cada iteración, el iterador se ejecuta para generar el siguiente elemento en lugar de referirse a la lista de expresiones original.

Es necesario que funcione de esta manera, si lo piensas. La expresión para la secuencia de un bucle for podría ser cualquier cosa:

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

No podemos consultar la secuencia en cada paso a través del bucle, o aquí terminaríamos leyendo el siguiente lote de 5 bytes la segunda vez. Naturalmente, Python debe almacenar de alguna manera el resultado de la expresión en privado antes de que comience el ciclo.


¿Están en diferentes ámbitos?

No. Para confirmar esto, puede mantener una referencia al diccionario de scope original ( locals () ) y notar que, de hecho, está utilizando las mismas variables dentro del bucle:

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

¿Qué está pasando bajo el capó que permite que algo como esto funcione?

Sean Vieira mostró exactamente lo que está pasando debajo del capó, pero para describirlo en un código de Python más legible, su bucle for es esencialmente equivalente a este bucle while:

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

Esto es diferente del enfoque de indexación tradicional para la iteración que vería en versiones anteriores de Java, por ejemplo:

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

Este enfoque fallaría cuando la variable de elemento y la variable de secuencia sean las mismas, porque la secuencia x ya no estaría disponible para buscar el siguiente índice después de la primera vez que se reasignó x al primer elemento.

Sin embargo, con el enfoque anterior, la primera línea ( it = iter(x) ) solicita un objeto iterador, que es el responsable de proporcionar el siguiente elemento a partir de ese momento. La secuencia a la que x apuntó originalmente ya no necesita ser accedida directamente.

Es la diferencia entre una variable (x) y el objeto al que apunta (la lista). Cuando se inicia el bucle for, Python toma una referencia interna al objeto al que apunta x. Utiliza el objeto y no lo que x hace referencia en un momento dado.

Si reasignas x, el bucle for no cambia. Si x apunta a un objeto mutable (por ejemplo, una lista) y usted cambia ese objeto (por ejemplo, eliminar un elemento), los resultados pueden ser impredecibles.

Básicamente, el bucle for toma la lista x , y luego, almacenándola como una variable temporal, vuelve a asignar una x a cada valor en esa variable temporal. Por lo tanto, x es ahora el último valor en la lista.

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

Al igual que en este:

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

En este ejemplo, x se almacena en foo() como bar , por lo que aunque se está reasignando x , todavía existe (ed) en foo() para que podamos usarlo para activar nuestro bucle for .

x ya no hace referencia a la lista x original, por lo que no hay confusión. Básicamente, python recuerda que está iterando sobre la lista x original, pero tan pronto como comienza a asignar el valor de iteración (0,1,2, etc.) al nombre x , ya no hace referencia a la lista x original. El nombre se reasigna al valor de iteración.

 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