una tuplas por metodos llenar listas lista elementos diccionarios comprensiĆ³n anidadas agregar python generator list-comprehension yield generator-expression

python - tuplas - ceder en listas de comprensiones y expresiones generadoras



metodos de listas en python (1)

Nota : esto fue un error en el manejo del yield de CPython en comprensiones y expresiones generadoras, corregido en Python 3.8, con una advertencia de desaprobación en Python 3.7. Consulte el informe de errores de Python y las entradas de Novedades para Python 3.7 y Python 3.8 .

Las expresiones de generador y las comprensiones de conjunto y dictado se compilan en objetos de función (generador). En Python 3, las comprensiones de listas reciben el mismo tratamiento; Todos son, en esencia, un nuevo ámbito anidado.

Puede ver esto si intenta desmontar una expresión generadora:

>>> dis.dis(compile("(i for i in range(3))", '''', ''exec'')) 1 0 LOAD_CONST 0 (<code object <genexpr> at 0x10f7530c0, file "", line 1>) 3 LOAD_CONST 1 (''<genexpr>'') 6 MAKE_FUNCTION 0 9 LOAD_NAME 0 (range) 12 LOAD_CONST 2 (3) 15 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 18 GET_ITER 19 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 22 POP_TOP 23 LOAD_CONST 3 (None) 26 RETURN_VALUE >>> dis.dis(compile("(i for i in range(3))", '''', ''exec'').co_consts[0]) 1 0 LOAD_FAST 0 (.0) >> 3 FOR_ITER 11 (to 17) 6 STORE_FAST 1 (i) 9 LOAD_FAST 1 (i) 12 YIELD_VALUE 13 POP_TOP 14 JUMP_ABSOLUTE 3 >> 17 LOAD_CONST 0 (None) 20 RETURN_VALUE

Lo anterior muestra que una expresión de generador se compila en un objeto de código, cargada como una función ( MAKE_FUNCTION crea el objeto de función a partir del objeto de código). La .co_consts[0] nos permite ver el objeto de código generado para la expresión, y usa YIELD_VALUE tal como lo haría una función generadora.

Como tal, la expresión de yield funciona en ese contexto, ya que el compilador las ve como funciones disfrazadas.

Esto es un error yield no tiene lugar en estas expresiones. La gramática de Python antes de Python 3.7 lo permite (por eso el código es compilable), pero la Reference muestra que el uso de yield aquí no debería funcionar realmente:

La expresión de rendimiento solo se usa al definir una función generadora y, por lo tanto, solo se puede usar en el cuerpo de una definición de función.

Se ha confirmado que se trata de un error en el problema 10544 . La resolución del error es que usar el yield y el yield from generará un SyntaxError en Python 3.8 ; en Python 3.7 , genera una DeprecationWarning de DeprecationWarning para garantizar que el código deje de usar esta construcción. Verá la misma advertencia en Python 2.7.15 y versiones posteriores si usa el modificador de línea de comando -3 que habilita las advertencias de compatibilidad con Python 3.

La advertencia 3.7.0b1 se ve así; convertir las advertencias en errores le ofrece una excepción SyntaxError , como lo haría en 3.8:

>>> [(yield i) for i in range(3)] <stdin>:1: DeprecationWarning: ''yield'' inside list comprehension <generator object <listcomp> at 0x1092ec7c8> >>> import warnings >>> warnings.simplefilter(''error'') >>> [(yield i) for i in range(3)] File "<stdin>", line 1 SyntaxError: ''yield'' inside list comprehension

Las diferencias entre cómo funcionan los yield en una comprensión de listas y los yield en una expresión generadora provienen de las diferencias en cómo se implementan estas dos expresiones. En Python 3, la comprensión de una lista utiliza llamadas LIST_APPEND para agregar la parte superior de la pila a la lista que se está LIST_APPEND , mientras que una expresión generadora en su lugar produce ese valor. Agregar (yield <expr>) simplemente agrega otro código de operación YIELD_VALUE a:

>>> dis.dis(compile("[(yield i) for i in range(3)]", '''', ''exec'').co_consts[0]) 1 0 BUILD_LIST 0 3 LOAD_FAST 0 (.0) >> 6 FOR_ITER 13 (to 22) 9 STORE_FAST 1 (i) 12 LOAD_FAST 1 (i) 15 YIELD_VALUE 16 LIST_APPEND 2 19 JUMP_ABSOLUTE 6 >> 22 RETURN_VALUE >>> dis.dis(compile("((yield i) for i in range(3))", '''', ''exec'').co_consts[0]) 1 0 LOAD_FAST 0 (.0) >> 3 FOR_ITER 12 (to 18) 6 STORE_FAST 1 (i) 9 LOAD_FAST 1 (i) 12 YIELD_VALUE 13 YIELD_VALUE 14 POP_TOP 15 JUMP_ABSOLUTE 3 >> 18 LOAD_CONST 0 (None) 21 RETURN_VALUE

El código de operación YIELD_VALUE en los índices de bytecode 15 y 12 respectivamente es extra, un cuco en el nido. Entonces, para la lista-comprensión-convertida en generador, tiene 1 rendimiento que produce la parte superior de la pila cada vez (reemplazando la parte superior de la pila con el valor de retorno del yield ), y para la variante de expresión del generador, usted produce la parte superior de la pila ( el número entero) y luego ceder nuevamente , pero ahora la pila contiene el valor de retorno del yield y no obtienes None esa segunda vez.

Para la comprensión de la lista, entonces, la salida del objeto de la list prevista aún se devuelve, pero Python 3 ve esto como un generador, por lo que el valor de retorno se adjunta a la excepción StopIteration como el atributo de value :

>>> from itertools import islice >>> listgen = [(yield i) for i in range(3)] >>> list(islice(listgen, 3)) # avoid exhausting the generator [0, 1, 2] >>> try: ... next(listgen) ... except StopIteration as si: ... print(si.value) ... [None, None, None]

Esos objetos None son los valores de retorno de las expresiones de yield .

Y para reiterar esto nuevamente; este mismo problema se aplica al diccionario y la comprensión de conjuntos en Python 2 y Python 3 también; en Python 2, los valores de retorno de yield todavía se agregan al diccionario deseado o al objeto establecido, y el valor de retorno se ''cede'' en último lugar en lugar de adjuntarlo a la excepción StopIteration :

>>> list({(yield k): (yield v) for k, v in {''foo'': ''bar'', ''spam'': ''eggs''}.items()}) [''bar'', ''foo'', ''eggs'', ''spam'', {None: None}] >>> list({(yield i) for i in range(3)}) [0, 1, 2, set([None])]

El siguiente comportamiento me parece bastante intuitivo (Python 3.4):

>>> [(yield i) for i in range(3)] <generator object <listcomp> at 0x0245C148> >>> list([(yield i) for i in range(3)]) [0, 1, 2] >>> list((yield i) for i in range(3)) [0, None, 1, None, 2, None]

Los valores intermedios de la última línea en realidad no siempre son None , son lo que send al generador, equivalente (supongo) al siguiente generador:

def f(): for i in range(3): yield (yield i)

Me parece gracioso que esas tres líneas funcionen en absoluto. La Reference dice que el yield solo está permitido en una definición de función (aunque puede que lo esté leyendo mal y / o simplemente se haya copiado de la versión anterior). Las primeras dos líneas producen un SyntaxError en Python 2.7, pero la tercera línea no.

Además, parece extraño

  • que una comprensión de la lista devuelve un generador y no una lista
  • y que la expresión generadora convertida en una lista y la comprensión de la lista correspondiente contienen valores diferentes.

¿Alguien podría proporcionar más información?