Подтвердить что ты не робот

Генератор Понимание различного вывода из понимания списка?

Я получаю разные результаты при использовании понимания списка в сравнении с пониманием генератора. Это ожидаемое поведение или ошибка?

Рассмотрим следующую настройку:

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
4b9b3361

Ответ 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]