Выражение генератора в понимании списка не работает должным образом - PullRequest
0 голосов
/ 03 мая 2018

Следующий код выдает ожидаемый результат:

# using a list comprehension as the first expression to a list comprehension
>>> l = [[i*2+x for x in j] for i,j in zip([0,1],[range(4),range(4)])]
>>> l[0]
[0, 1, 2, 3]
>>> l[1]
[2, 3, 4, 5]

Однако, когда я вместо этого использую выражение генератора, я получаю другой результат:

# using a generator expression as the first expression
>>> l = [(i*2+x for x in j) for i,j in zip([0,1],[range(4),range(4)])]
>>> list(l[0])
[2, 3, 4, 5]
>>> list(l[1])
[2, 3, 4, 5]
>>> list(l[0])
[]
>>> list(l[1])
[]
>>> l
[<generator object <listcomp>.<genexpr> at 0x7fddfa413ca8>, <generator object <listcomp>.<genexpr> at 0x7fddfa413c50>]

Я понимаю, что выражения генератора могут использоваться только один раз, однако у меня возникают проблемы с рассуждением, почему в этом сценарии я получаю один и тот же список дважды, тем более что объекты генератора выглядят уникальными.

Что мне здесь не хватает? Это было проверено на Python 3.6.5.

Ответы [ 2 ]

0 голосов
/ 03 мая 2018

Поскольку i связан с 1, в то время как каждое выражение генератора выполняется . Выражения генератора не фиксируют действующие привязки в то время, когда они созданы - они используют привязки, действующие во время их выполнения.

>>> j = 100000
>>> e = (j for i in range(3))
>>> j = -6
>>> list(e)
[-6, -6, -6]
0 голосов
/ 03 мая 2018

Объекты-генераторы уникальны, но они ссылаются на i и j, но понимание списка прекращается (что, по сути, создает область действия функции, точно так же, как выражения генератора внутри понимания списка). Таким образом, i и j имеют значения i == 1 и j == range(4). Вы даже можете проанализировать это:

In [1]: l = [(i*2+x for x in j) for i,j in zip([0,1],[range(4),range(4)])]

In [2]: g = l[0]

In [3]: g.gi_frame.f_locals
Out[3]: {'.0': <range_iterator at 0x10e9be960>, 'i': 1}

По сути, это та же самая причина, по которой происходит это часто удивительное поведение:

In [4]: fs = [lambda: i for i in range(3)]

In [5]: fs[0]
Out[5]: <function __main__.<listcomp>.<lambda>()>

In [6]: fs[0]()
Out[6]: 2

In [7]: fs[1]()
Out[7]: 2

In [8]: fs[2]()
Out[8]: 2

Это можно исправить с помощью того же решения, которое заключается в создании другой охватывающей области действия , которая локально связывает переменные с чем-то, что не изменится. Использование функции (здесь лямбда, но это может быть обычная функция) будет прекрасно работать:

In [9]: l = [(lambda i, j: (i*2+x for x in j))(i, j) for i,j in zip([0,1],[range(4),range(4)])]

In [10]: list(l[0])
Out[10]: [0, 1, 2, 3]

In [11]: list(l[1])
Out[11]: [2, 3, 4, 5]

Хотя, возможно, для ясности, я буду использовать разные имена параметров, чтобы сделать более очевидным, что происходит:

In [12]: l = [(lambda a, b: (a*2+x for x in b))(i, j) for i,j in zip([0,1],[range(4),range(4)])]

In [13]: list(l[0])
Out[13]: [0, 1, 2, 3]

In [14]: list(l[1])
Out[14]: [2, 3, 4, 5]
...