que objeto metodos listas python for-loop variable-assignment python-internals

objeto - ¿Por qué puedo usar el mismo nombre para iterador y secuencia en Python for loop?



objeto iterable en python (6)

Básicamente, el bucle for toma en la lista x , y luego, al almacenar eso como una variable temporal, reasigna 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 esto:

>>> 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 x se reasigne, todavía existe (ed) en foo() para que podamos usarlo para activar nuestro ciclo for .

Esta es más una pregunta conceptual. Recientemente vi un fragmento de código en Python (funcionó en 2.7, y también podría haber sido ejecutado en 2.5) en el que un bucle for usaba el mismo nombre para la lista que estaba siendo iterada y 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 del ciclo, pero no entiendo por qué podría usar el mismo nombre de variable para sus partes del bucle for y hacer que funcione según lo previsto. ¿Están en diferentes ámbitos? ¿Qué está pasando bajo el capó que permite que algo como esto funcione?


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 del objeto al que señala x. Utiliza el objeto y no lo que sucede x para referenciar en un momento dado.

Si reasigna x, el ciclo for no cambia. Si x apunta a un objeto mutable (p. Ej., Una lista) y usted cambia ese objeto (por ejemplo, elimina un elemento) los resultados pueden ser impredecibles.


Es necesario que funcione de esta manera, si lo piensas bien. La expresión de 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 pasada 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 de forma privada antes de que comience el ciclo.

¿Están en diferentes ámbitos?

No. Para confirmar esto, puede mantener una referencia al diccionario de alcance original ( locals() ) y observar que de hecho está usando las mismas variables dentro del ciclo:

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 sucede bajo el capó, pero para describirlo en un código python más legible, su ciclo for es esencialmente equivalente a este ciclo while:

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

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

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

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

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


Lo que nos dice:

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 nuevamente .

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

Cuando el nombre x se sobrescribe para apuntar a cada valor dentro de la lista conocida anteriormente como x Python, no tiene problemas para encontrar el iterador porque nunca necesita volver a buscar el nombre x para finalizar el protocolo de iteración.


Usando su código de ejemplo como referencia principal

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

Me gustaría que refieras la sección 7.3. La declaración para en el manual

Extracto 1

La lista de expresiones se evalúa una vez; debería producir un objeto iterable. Se crea un iterador para el resultado de la expression_list.

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 a un objeto iterable. Incluso si la variable, la referencia simbólica cambia su lealtad, ya que la expression-list no se evalúa nuevamente, no hay impacto para 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 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

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

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


x ya no se refiere 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 comienzas 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