Obtengo un resultado diferente cuando uso una comprensión de lista versus una comprensión de generador. ¿Es este comportamiento esperado o un error?
Considere la siguiente configuración:
all_configs = [ {'a': 1, 'b':3}, {'a': 2, 'b':2} ] unique_keys = ['a','b']Si luego ejecuto el siguiente código, obtengo:
print(list(zip(*( [c[k] for k in unique_keys] for c in all_configs)))) >>> [(1, 2), (3, 2)] # note the ( vs [ print(list(zip(*( (c[k] for k in unique_keys) for c in all_configs)))) >>> [(2, 2), (2, 2)]Esto está en python 3.6.0:
Python 3.6.0 (default, Dec 24 2016, 08:01:42) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwinEn una lista de comprensión, las expresiones se evalúan con entusiasmo. En una expresión de generador, solo se buscan según sea necesario.
Por lo tanto, a medida que la expresión del generador itera sobre for c in all_configs , se refiere a c[k] pero solo busca c después de que finaliza el ciclo, por lo que solo usa el valor más reciente para ambas tuplas. Por el contrario, la comprensión de listas se evalúa inmediatamente, por lo que crea una tupla con el primer valor de c y otra tupla con el segundo valor de c .
Considere este pequeño ejemplo:
>>> r = range(3) >>> i = 0 >>> a = [i for _ in r] >>> b = (i for _ in r) >>> i = 3 >>> print(*a) 0 0 0 >>> print(*b) 3 3 3 Al crear a , el intérprete creó esa lista inmediatamente, buscando el valor de i tan pronto como se evaluó. Al crear b , el intérprete simplemente configuró ese generador y en realidad no iteró sobre él y buscó el valor de i . Las llamadas de print le dijeron al intérprete que evaluara esos objetos. a ya existía como una lista completa en la memoria con el valor anterior de i , pero b se evaluó en ese momento, y cuando buscó el valor de i , encontró el nuevo valor.
Para ver qué está pasando, reemplace c[k] con una función con un efecto secundario:
def f(c,k): print(c,k) return c[k] print("listcomp") print(list(zip(*( [f(c,k) for k in unique_keys] for c in all_configs)))) print("gencomp") print(list(zip(*( (f(c,k) for k in unique_keys) for c in all_configs))))producción:
listcomp {'a': 1, 'b': 3} a {'a': 1, 'b': 3} b {'a': 2, 'b': 2} a {'a': 2, 'b': 2} b [(1, 2), (3, 2)] gencomp {'a': 2, 'b': 2} a {'a': 2, 'b': 2} a {'a': 2, 'b': 2} b {'a': 2, 'b': 2} b [(2, 2), (2, 2)] c en las expresiones del generador se evalúa después de que se haya completado el ciclo externo:
c lleva el último valor que tomó en el bucle exterior.
En el caso de comprensión de listas, c se evalúa de inmediato.
(tenga en cuenta que aabb vs abab también debido a la ejecución al comprimir vs ejecución a la vez)
tenga en cuenta que puede mantener la forma de "generador" de hacerlo (sin crear la lista temporal) pasando c al map para que se almacene el valor actual:
print(list(zip(*( map(c.get,unique_keys) for c in all_configs)))) en Python 3, el map no crea una list , pero el resultado sigue siendo correcto: [(1, 2), (3, 2)]
Esto sucede porque la llamada zip(*) resultó en la evaluación del generador externo y este externo devolvió dos generadores más.
(c[k], print(c)) for k in unique_keys) La evaluación del generador externo movió c al segundo dict: {'a': 2, 'b':2} .
Ahora, cuando estamos evaluando estos generadores individualmente, buscan c en alguna parte, y como su valor ahora es {'a': 2, 'b':2} obtienes el resultado como [(2, 2), (2, 2)] .
Manifestación:
>>> def my_zip(*args): ... print(args) ... for arg in args: ... print (list(arg)) ... ... my_zip(*((c[k] for k in unique_keys) for c in all_configs)) ...Producción:
# We have two generators now, means it has looped through `all_configs`. (<generator object <genexpr>.<genexpr> at 0x104415c50>, <generator object <genexpr>.<genexpr> at 0x10416b1a8>) [2, 2] [2, 2] La lista de comprensión, por otro lado, se evalúa de inmediato y puede obtener el valor del valor actual de c no su último valor.
c ? Utilice una función interna y una función de generador. La función interna puede ayudarnos a recordar el valor de c usando el argumento predeterminado.
>>> def solve(): ... for c in all_configs: ... def func(c=c): ... return (c[k] for k in unique_keys) ... yield func() ... >>> >>> list(zip(*solve())) [(1, 2), (3, 2)]