Генератор Понимание различного вывода из понимания списка?
Я получаю разные результаты при использовании понимания списка в сравнении с пониманием генератора. Это ожидаемое поведение или ошибка?
Рассмотрим следующую настройку:
all_configs = [
{'a': 1, 'b':3},
{'a': 2, 'b':2}
]
unique_keys = ['a','b']
Если я запустил следующий код, я получаю:
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)]
Это на 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 darwin
Ответы
Ответ 1
В понимании списка выражения оцениваются с нетерпением. В выражении генератора они выглядят только по мере необходимости.
Таким образом, поскольку выражение генератора итерации над for c in all_configs
, оно ссылается на c[k]
, но только смотрит вверх c
после того, как цикл завершен, поэтому он использует только последнее значение для обоих кортежей. Напротив, понимание списка оценивается немедленно, поэтому он создает кортеж с первым значением c
и другим кортежем со вторым значением c
.
Рассмотрим этот небольшой пример:
>>> 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
При создании a
интерпретатор создавал этот список немедленно, просматривая значение i
, как только он был оценен. При создании b
интерпретатор просто настраивал этот генератор и фактически не перебирал его и не искал значение i
. При вызовах print
интерпретатор интерпретировал эти объекты. a
уже существовал как полный список в памяти со старым значением i
, но b
был оценен в этой точке, и когда он искал значение i
, он обнаружил новое значение.
Ответ 2
Чтобы узнать, что происходит, замените c[k]
на функцию с побочным эффектом:
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))))
выход:
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
в выражениях генератора оценивается после завершения внешнего цикла:
c
имеет последнее значение, которое потребовалось во внешнем цикле.
В случае распознавания списка c
оценивается сразу.
(обратите внимание, что aabb
vs abab
тоже из-за выполнения при застегивании и одновременном выполнении)
обратите внимание, что вы можете сохранить "генератор" способ сделать это (не создавая временный список), передав c
в map
, чтобы сохранить текущее значение:
print(list(zip(*( map(c.get,unique_keys) for c in all_configs))))
в Python 3, map
не создает list
, но результат все еще в порядке: [(1, 2), (3, 2)]
Ответ 3
Это происходит потому, что вызов zip(*)
привел к оценке внешнего генератора, и этот внешний возвращал еще два генератора.
(c[k], print(c)) for k in unique_keys)
Оценка внешнего генератора переместила c
во второй dict: {'a': 2, 'b':2}
.
Теперь, когда мы каждый раз оцениваем эти генераторы, мы ищем c
где-то, и поскольку его значение теперь {'a': 2, 'b':2}
, вы получаете результат как [(2, 2), (2, 2)]
.
Demo:
>>> 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))
...
Вывод:
# 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]
С другой стороны, понимание списков сразу оценивает и может отображать значение текущего значения c
, а не его последнее значение.
Как заставить его использовать правильное значение c
?
Используйте функцию внутренней функции и генератора. Внутренняя функция может помочь нам запомнить значение c
с использованием аргумента по умолчанию.
>>> 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)]
Ответ 4
Оба являются объектами-генераторами. Первый - это просто генератор, а второй генератор в генераторе
print list( [c[k] for k in unique_keys] for c in all_configs)
[[1, 3], [2, 2]]
print list( (c[k] for k in unique_keys) for c in all_configs)
[<generator object <genexpr> at 0x000000000364A750>, <generator object <genexpr> at 0x000000000364A798>]
Когда вы используете zip (* в первом выражении, ничего не происходит, потому что это один генератор, который будет возвращать список, аналогичный списку(), поэтому он возвращает результат, который вы ожидаете. Второй раз он застегивает генераторы, создающие список с первым генератором и список со вторым генератором. Те генераторы, которые там есть, имеют отличный результат, чем генератор первого выражения.
Это будет сжатие списка:
print [c[k] for k in unique_keys for c in all_configs]
[1, 2, 3, 2]