¿Cuál es la diferencia entre las listas de comprensión y las de generador con yield
dentro? Ambos devuelven un objeto generador ( listcomp
y genexpr
respectivamente), pero luego de una evaluación completa, este último agrega lo que parece ser un Superfluo None
s.
>>> list([(yield from a) for a in zip("abcde", itertools.cycle("12"))]) ['a', '1', 'b', '2', 'c', '1', 'd', '2', 'e', '1'] >>> list(((yield from a) for a in zip("abcde", itertools.cycle("12")))) ['a', '1', None, 'b', '2', None, 'c', '1', None, 'd', '2', None, 'e', '1', None]
¿Cómo? ¿Cuál es la explicación científica?
TLDR: una expresión generadora utiliza un yield
implícito, que devuelve None
del yield from
expresión.
En realidad, hay dos cosas que se comportan de manera diferente aquí. Su lista de comprensión es en realidad tirada …
Comprender esto es más fácil si transformas las expresiones en funciones equivalentes. Para mayor claridad, escribamos esto:
listcomp = [ for a in b] def listfunc(): result = [] for a in b: result.append( ) return result gencomp = ( for a in b) def genfunc(): for a in b: yield
Para replicar las expresiones iniciales, la clave es reemplazar
con (yield from a)
. Este es un simple reemplazo textual:
def listfunc(): result = [] for a in b: result.append((yield from a)) return result def genfunc(): for a in b: yield (yield from a)
Con b = ((1,), (2,))
, esperaríamos la salida 1, 2
. De hecho, ambos replican la salida de sus respectivas formas de expresión / comprensión.
Como se explica en otra parte , el yield (yield from a)
debe hacerte sospechar. Sin embargo, result.append((yield from a))
debería hacerte temblar …
Veamos primero el generador. Otra reescritura hace obvio lo que está pasando:
def genfunc(): for a in b: result = (yield from a) yield result
Para que esto sea válido, el result
debe tener un valor, a saber, None
. El generador no yield
la expresión (yield from a)
, sino su resultado. Solo se obtiene el contenido de a
como efecto secundario de evaluar la expresión.
Si verifica el tipo de su “lista de comprensión”, no es una list
, es un generator
.
es solo su nombre. Sí, eso no es una Luna, es un generador completamente funcional.
¿Recuerdas cómo nuestra transformación pone un yield from
dentro de una función? Yepp, así es como se define un generador! Aquí está nuestra versión de la función, esta vez con la print
rociada en ella:
def listfunc(): result = [] for a in b: result.append((yield from a)) print(result[-1]) print(result) return result
La evaluación de la list(listfunc())
imprime None
, None
y [None, None]
y devuelve [1, 2]
. ¡Su lista real contiene aquellos None
que también se colaron en el generador! Sin embargo, se desecha y el resultado nuevamente es solo un efecto secundario. Esto es lo que realmente sucede:
listfunc
. list
itera sobre él …
yield from a
proporciona los valores de a
y devuelve None
None
se almacena en la lista de resultados Al final de la iteración …
return
eleva StopIteration
con un valor de [None, None]
list
ignora esto y desecha el valor. Moraleja de esta historia
No use el yield from
interior de las comprensiones. No hace lo que piensas que hace.
El valor del yield from
expresión es None
. El hecho de que su segundo ejemplo sea una expresión generadora significa que ya está produciendo implícitamente desde el iterador, por lo que también producirá el valor del yield from
expresión. Vea esto para una respuesta más detallada.