ordereddict - python create dictionary from list
(list | set | dict) la comprensión que contiene una expresión de rendimiento no devuelve a(list | set | dict) (2)
Python 3.3
He construido esta pieza ligeramente críptica de python 3.3:
>>> [(yield from (i, i + 1, i)) for i in range(5)]
<generator object <listcomp> at 0x0000008666D96900>
>>> list(_)
[0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4]
Si uso un generador de comprensión dentro de un constructor de listas, obtengo un resultado diferente:
>>> list((yield from (i, i + 1, i)) for i in range(5))
[0, 1, 0, None, 1, 2, 1, None, 2, 3, 2, None, 3, 4, 3, None, 4, 5, 4, None]
¿Por qué la lista de comprensión no devuelve una lista?
Python 2.7
Puedo obtener un efecto extraño similar en python 2 (usando una comprensión establecida, porque las comprensiones de lista tienen un alcance impar):
>>> {(yield i) for i in range(5)}
<generator object <setcomp> at 0x0000000004A06120>
>>> list(_)
[0, 1, 2, 3, 4, {None}]
Y cuando se utiliza un generador de comprensión:
>>> list((yield i) for i in range(5))
[0, None, 1, None, 2, None, 3, None, 4, None]
¿De dónde vino esa {None}
?
Las comprensiones de lista (conjunto, dict) se traducen a una estructura de código diferente de las expresiones generadoras. Veamos un conjunto de comprensión:
def f():
return {i for i in range(10)}
dis.dis(f.__code__.co_consts[1])
2 0 BUILD_SET 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_FAST 1 (i)
15 SET_ADD 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Compare con la expresión del generador equivalente:
def g():
return (i for i in range(10))
dis.dis(g.__code__.co_consts[1])
2 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
Notará que donde la expresión del generador tiene un yield
, la comprensión del conjunto almacena un valor directamente en el conjunto que está creando.
Esto significa que si agrega una expresión de yield
en el cuerpo de una expresión de generador, se trata indistintamente del yield
que el lenguaje construye para el cuerpo del generador; como resultado, obtienes dos (o más) valores por iteración.
Sin embargo, si agrega un yield
a una comprensión de lista (set, dict), la comprensión se transforma de una función que construye una lista (set, dict) en un generador que ejecuta las declaraciones de yield
y luego devuelve la lista construida (set, dict) . El {None}
en el resultado de la comprensión del conjunto es el conjunto construido a partir de cada una de las None
las que se evalúan las expresiones de yield
.
Finalmente, ¿por qué Python 3.3 no produce un {None}
? (Tenga en cuenta que las versiones anteriores de Python 3 sí lo hacen). Es debido a PEP 380 (también conocido como yield from
soporte). Antes de Python 3.3, un return
en un generador es un SyntaxError: ''return'' with argument inside generator
; Por lo tanto, nuestras comprensiones de yield
están explotando un comportamiento indefinido, pero el resultado real del opcode RETURN_VALUE
es generar simplemente otro valor (final) del generador. En Python 3.3, el return value
se admite explícitamente; un RETURN_VALUE
operación RETURN_VALUE
hace que se RETURN_VALUE
una StopIteration
, que tiene el efecto de detener el generador sin producir un valor final.
Usando esto como una referencia:
Explicación de Python 3
Esta:
values = [(yield from (i, i + 1, i)) for i in range(5)]
Se traduce a lo siguiente en Python 3.x:
def _tmpfunc():
_tmp = []
for x in range(5):
_tmp.append(yield from (i, i + 1, i))
return _tmp
values = _tmpfunc()
Lo que resulta en values
contienen un generador.
Ese generador luego cederá de cada (i, i + 1, i)
, hasta que finalmente alcance la declaración de retorno. En Python 3, esto generará throw StopIteration(_tmp)
; sin embargo, el constructor de list
ignora esta excepción.
Por otro lado, esto:
list((yield from (i, i + 1, i)) for i in range(5))
Se traduce a lo siguiente en Python 3.x:
def _tmpfunc():
for x in range(5):
yield (yield from (i, i + 1, i))
values = list(_tmpfunc())
Esta vez, cada vez que se completa el yield from
, se evalúa como None
, que luego se yield
entre los otros valores.