Diferencia entre la comprensión de la lista y la comprensión del generador con ‘rendimiento’ dentro

¿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 …

  • Una vez más con claridad.

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 …

  • Dando la respuesta

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.

  • Volviendo a la pregunta

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:

  • Se crea un generador al evaluar la lista comprensión / listfunc .
  • Alimentarlo a la 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]
    • El constructor de 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.