python python-2.7 list-comprehension generator-expression

python - Salida inesperada de la lista(generador)



python-2.7 list-comprehension (6)

Tengo una lista y una función lambda definida como

In [1]: i = lambda x: a[x] In [2]: alist = [(1, 2), (3, 4)]

Luego intento dos métodos diferentes para calcular una suma simple

Primer método

In [3]: [i(0) + i(1) for a in alist] Out[3]: [3, 7]

Segundo método.

In [4]: list(i(0) + i(1) for a in alist) Out[4]: [7, 7]

Ambos resultados son inesperadamente diferentes. ¿Por qué está sucediendo eso?


Cosas importantes para entender aquí son

  1. Las expresiones generadoras crearán objetos de función internamente, pero la comprensión de la lista no.

  2. ambos vincularán la variable de bucle a los valores y las variables de bucle estarán en el ámbito actual si aún no están creadas.

Veamos los códigos de bytes de la expresión del generador.

>>> dis(compile(''(i(0) + i(1) for a in alist)'', ''string'', ''exec'')) 1 0 LOAD_CONST 0 (<code object <genexpr> at ...>) 3 MAKE_FUNCTION 0 6 LOAD_NAME 0 (alist) 9 GET_ITER 10 CALL_FUNCTION 1 13 POP_TOP 14 LOAD_CONST 1 (None) 17 RETURN_VALUE

Carga el objeto de código y luego lo convierte en una función. Permite ver el objeto de código real.

>>> dis(compile(''(i(0) + i(1) for a in alist)'', ''string'', ''exec'').co_consts[0]) 1 0 LOAD_FAST 0 (.0) >> 3 FOR_ITER 27 (to 33) 6 STORE_FAST 1 (a) 9 LOAD_GLOBAL 0 (i) 12 LOAD_CONST 0 (0) 15 CALL_FUNCTION 1 18 LOAD_GLOBAL 0 (i) 21 LOAD_CONST 1 (1) 24 CALL_FUNCTION 1 27 BINARY_ADD 28 YIELD_VALUE 29 POP_TOP 30 JUMP_ABSOLUTE 3 >> 33 LOAD_CONST 2 (None) 36 RETURN_VALUE

Como se ve aquí, el valor actual del iterador se almacena en la variable a . Pero como hacemos de este un objeto de función, el a creado solo será visible dentro de la expresión del generador.

Pero en el caso de la lista de comprensión,

>>> dis(compile(''[i(0) + i(1) for a in alist]'', ''string'', ''exec'')) 1 0 BUILD_LIST 0 3 LOAD_NAME 0 (alist) 6 GET_ITER >> 7 FOR_ITER 28 (to 38) 10 STORE_NAME 1 (a) 13 LOAD_NAME 2 (i) 16 LOAD_CONST 0 (0) 19 CALL_FUNCTION 1 22 LOAD_NAME 2 (i) 25 LOAD_CONST 1 (1) 28 CALL_FUNCTION 1 31 BINARY_ADD 32 LIST_APPEND 2 35 JUMP_ABSOLUTE 7 >> 38 POP_TOP 39 LOAD_CONST 2 (None) 42 RETURN_VALUE

No hay creación explícita de funciones y la variable a se crea en el ámbito actual. Por lo tanto, a se filtra en el alcance actual.

Con esta comprensión, vamos a abordar su problema.

>>> i = lambda x: a[x] >>> alist = [(1, 2), (3, 4)]

Ahora, cuando creas una lista con comprensión,

>>> [i(0) + i(1) for a in alist] [3, 7] >>> a (3, 4)

puede ver que a se filtra al ámbito actual y aún está vinculado al último valor de la iteración.

Entonces, cuando se itera la expresión del generador después de la comprensión de la lista, la función lambda usa la a filtrada. Es por eso que está obteniendo [7, 7] , ya que a todavía está vinculado a (3, 4) .

Pero, si primero itera la expresión del generador, entonces la a estará vinculada a los valores de alist y no se filtrará al alcance actual, ya que la expresión del generador se convierte en una función. Entonces, cuando la función lambda intenta acceder a , no puede encontrarla en ninguna parte. Es por eso que falla con el error.

Nota: El mismo comportamiento no se puede observar en Python 3.x, porque la filtración se evita al crear funciones para las listas de comprensión también. Es posible que desee leer más sobre esto en la publicación del blog History of Python, python-history.blogspot.ie/2010/06/… , escrita por el propio Guido.


Debes hacer a parámetro a tu función lambda. Esto funciona como se esperaba:

In [10]: alist = [(1, 2), (3, 4)] In [11]: i = lambda a, x: a[x] In [12]: [i(a, 0) + i(a, 1) for a in alist] Out[12]: [3, 7] In [13]: list(i(a, 0) + i(a, 1) for a in alist) Out[13]: [3, 7]

Una forma alternativa de obtener el mismo resultado sería:

In [14]: [sum(a) for a in alist] Out[14]: [3, 7]

EDITAR esta respuesta es solo una solución simple y no es una respuesta real a la pregunta. El efecto observado es un poco más complejo, vea mi otra respuesta .


Después de que se ejecute [i(0) + i(1) for a in alist] , a convierte en (3,4) .

Luego, cuando se ejecuta la línea de abajo:

list(i(0) + i(1) for a in alist)

(3,4) valor es usado tanto por la función lambda i como el valor de a , por lo que se imprime [7,7].

En su lugar, debe definir sus funciones lambda con dos parámetros a y x .

i = lambda a,x : a[x]


Este comportamiento se ha corregido en Python 3. Cuando usa una lista de comprensión [i(0) + i(1) for a in alist] , definirá a en su ámbito circundante que es accesible para i . En una nueva list(i(0) + i(1) for a in alist) sesiones list(i(0) + i(1) for a in alist) producirá un error.

>>> i = lambda x: a[x] >>> alist = [(1, 2), (3, 4)] >>> list(i(0) + i(1) for a in alist) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <genexpr> File "<stdin>", line 1, in <lambda> NameError: global name ''a'' is not defined

Una lista de comprensión no es un generador: expresiones de generador y listas de comprensión .

Las expresiones del generador están rodeadas por paréntesis ("()") y las comprensiones de la lista están rodeadas por corchetes ("[]").

En su ejemplo, la list() como clase tiene su propio ámbito de variables y tiene acceso a variables globales como máximo. Cuando uses eso, buscaré a espacio dentro de ese alcance. Intenta esto en una nueva sesión:

>>> i = lambda x: a[x] >>> alist = [(1, 2), (3, 4)] >>> [i(0) + i(1) for a in alist] [3, 7] >>> a (3, 4)

Compáralo con esto en otra sesión:

>>> i = lambda x: a[x] >>> alist = [(1, 2), (3, 4)] >>> l = (i(0) + i(1) for a in alist) <generator object <genexpr> at 0x10e60db90> >>> a Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name ''a'' is not defined >>> [x for x in l] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <genexpr> File "<stdin>", line 1, in <lambda> NameError: global name ''a'' is not defined

Cuando ejecute la list(i(0) + i(1) for a in alist) pasará un generador (i(0) + i(1) for a in alist) a la clase de list que intentará convertirla a una lista en su propio alcance antes de devolver la lista. Para este generador que no tiene acceso dentro de la función lambda, la variable a no tiene significado.

El objeto generador <generator object <genexpr> at 0x10e60db90> objeto <generator object <genexpr> at 0x10e60db90> ha perdido el nombre de la variable a . Luego, cuando la list intenta llamar al generador, la función lambda generará un error para undefined a .

El comportamiento de las listas de comprensión en contraste con los generadores también se menciona here :

Las comprensiones de lista también "filtran" su variable de bucle en el ámbito circundante. Esto también cambiará en Python 3.0, de modo que la definición semántica de una comprensión de lista en Python 3.0 será equivalente a list (). Python 2.4 y las versiones posteriores deben emitir una advertencia de desaprobación si la variable de bucle de una comprensión de lista tiene el mismo nombre que una variable utilizada en el ámbito que rodea inmediatamente.

En python3:

>>> i = lambda x: a[x] >>> alist = [(1, 2), (3, 4)] >>> [i(0) + i(1) for a in alist] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <listcomp> File "<stdin>", line 1, in <lambda> NameError: name ''a'' is not defined


Ver mi otra respuesta para una solución. Pero pensando un poco más acerca de, el problema parece ser un poco más complejo. Creo que hay varios problemas aquí:

  • Cuando haces i = lambda x: a[x] , la variable a no es un parámetro de la función, esto se llama un closure . Esto es igual tanto para las expresiones lambda como para las definiciones de funciones normales.

  • Python aparentemente hace ''enlace tardío'', lo que significa que el valor de las variables que cerró solo se busca en el momento en que llama a la función. Esto puede llevar a various results inesperados.

  • En Python 2, hay una diferencia entre las comprensiones de lista, que filtran su variable de bucle, y las expresiones generadoras, en las cuales la variable de bucle no se filtra (consulte este PEP para más detalles). Esta diferencia se ha eliminado en Python 3, donde una comprensión de lista es un acceso directo para list(generater_expression) . No estoy seguro, pero esto probablemente significa que las comprensiones de la lista de Python2 se ejecutan en su ámbito externo, mientras que las expresiones generadoras y las comprensiones de la lista de Python3 crean su propio ámbito interno.

Demostración (en Python2):

In [1]: def f(): # closes over a from global scope ...: return 2 * a ...: In [2]: list(f() for a in range(5)) # does not find a in global scope [...] NameError: global name ''a'' is not defined In [3]: [f() for a in range(5)] # executes in global scope, so f finds a. Also leaks a=8 Out[3]: [0, 2, 4, 6, 8] In [4]: list(f() for a in range(5)) # finds a=8 in global scope Out[4]: [8, 8, 8, 8, 8]

En Python3:

In [1]: def f(): ...: return 2 * a ...: In [2]: list(f() for a in range(5)) # does not find a in global scope, does not leak a [...] NameError: name ''a'' is not defined In [3]: [f() for a in range(5)] # does not find a in global scope, does not leak a [...] NameError: name ''a'' is not defined In [4]: list(f() for a in range(5)) # a still undefined [...] NameError: name ''a'' is not defined


a está en alcance global. Entonces debería dar error.

La solución es:

i = lambda a, x: a[x]