Синтаксис расширенного вложенного списка
Я играл со списками, чтобы лучше понять их, и я столкнулся с неожиданным результатом, который я не могу объяснить. Я не задавал этот вопрос раньше, но если это вопрос/повтор, я извиняюсь.
Я по существу пытался написать генератор, который генерировал генераторы. Простой генератор, который использует понимание списка, будет выглядеть так:
(x for x in range(10) if x%2==0) # generates all even integers in range(10)
То, что я пытался сделать, это написать генератор, который сгенерировал два генератора - первый из которых породил четные числа в диапазоне (10), а второй из которых породил нечетные числа в диапазоне (10). Для этого я сделал:
>>> (x for x in range(10) if x%2==i for i in range(2))
<generator object <genexpr> at 0x7f6b90948f00>
>>> for i in g.next(): print i
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
UnboundLocalError: local variable 'i' referenced before assignment
>>> g.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> g = (x for x in range(10) if x%2==i for i in range(2))
>>> g
<generator object <genexpr> at 0x7f6b90969730>
>>> g.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
UnboundLocalError: local variable 'i' referenced before assignment
Я не понимаю, почему "i" ссылается перед назначением
Я думал, что это могло бы иметь какое-то отношение к i in range(2)
, поэтому я сделал:
>>> g = (x for x in range(10) if x%2==i for i in [0.1])
>>> g
<generator object <genexpr> at 0x7f6b90948f00>
>>> g.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
UnboundLocalError: local variable 'i' referenced before assignment
Для меня это не имело смысла, поэтому я подумал, что лучше сначала попробовать что-то проще. Поэтому я вернулся в списки и попробовал:
>>> [x for x in range(10) if x%2==i for i in range(2)]
[1, 1, 3, 3, 5, 5, 7, 7, 9, 9]
который, как я ожидал, будет таким же:
>>> l = []
>>> for i in range(2):
... for x in range(10):
... if x%2==i:
... l.append(x)
...
>>> l
[0, 2, 4, 6, 8, 1, 3, 5, 7, 9] # so where is my list comprehension malformed?
Но когда я это пробовал, это сработало:
>>> [[x for x in range(10) if x%2==i] for i in range(2)]
[[0, 2, 4, 6, 8], [1, 3, 5, 7, 9]] # so nested lists in nested list comprehension somehow affect the scope of if statements? :S
Поэтому я подумал, что это может быть проблема с тем, какой уровень охвата имеет оператор if
. Поэтому я пробовал это:
>>> [x for x in range(10) for i in range(2) if x%2==i]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
И теперь я совершенно смущен. Может кто-то объяснить это поведение. Я не понимаю, почему мои представления в списке кажутся искаженными, и я не понимаю, как работает определение операторов if
.
PS: Пока я проверяю вопрос, я понял, что это похоже на вопрос о домашнем задании - это не так.
Ответы
Ответ 1
вам нужно использовать некоторые круглые скобки:
((x for x in range(10) if x%2==i) for i in range(2))
Это не имело смысла для меня, поэтому я подумал, что лучше попробовать что-то проще сначала. Поэтому я вернулся в списки и попытался:
[ → > [x для x в диапазоне (10), если x% 2 == я для я в диапазоне (2)] [1, 1, 3, 3, 5, 5, 7, 7, 9, 9]
Это сработало, потому что предыдущее понимание списка утечки переменной я в охватывающую область действия и начало я для текущего. Попробуйте запустить новый интерпретатор python, и это приведет к ошибке из-за NameError. Противоположное поведение утечки было удалено в Python 3.
EDIT:
Эквивалент для цикла для:
(x for x in range(10) if x%2==i for i in range(2))
:
l = []
for x in range(10):
if x%2 == i:
for i in range(2):
l.append(x)
который также дает ошибку имени.
EDIT2:
в скобках:
((x for x in range(10) if x%2==i) for i in range(2))
эквивалентно:
li = []
for i in range(2):
lx = []
for x in range(10):
if x%2==i:
lx.append(x)
li.append(lx)
Ответ 2
Развернувшись на Ли Райан, ответьте немного:
something = (x для x в диапазоне (10), если x% 2 == я для я в диапазоне (2))
эквивалентно:
def _gen1():
for x in range(10):
if x%2 == i:
for i in range(2):
yield x
something = _gen1()
тогда как версия в скобках эквивалентна:
def _gen1():
def _gen2():
for x in range(10):
if x%2 == i:
yield x
for i in range(2):
yield _gen2()
something = _gen1()
Это фактически дает два генератора:
[<generator object <genexpr> at 0x02A0A968>, <generator object <genexpr> at 0x02A0A990>]
К сожалению, генераторы, которые он дает, несколько нестабильны, так как выход будет зависеть от того, как вы их потребляете:
>>> gens = ((x for x in range(10) if x%2==i) for i in range(2))
>>> for g in gens:
print(list(g))
[0, 2, 4, 6, 8]
[1, 3, 5, 7, 9]
>>> gens = ((x for x in range(10) if x%2==i) for i in range(2))
>>> for g in list(gens):
print(list(g))
[1, 3, 5, 7, 9]
[1, 3, 5, 7, 9]
Мой совет состоит в том, чтобы полностью написать функции генератора: я думаю, что пытаюсь получить правильную область видимости на i
, не делая этого, возможно, почти невозможно.
Ответ 3
Линейный эквивалент Ryan for-loop приводит меня к следующему, который, кажется, работает очень хорошо:
[x for i in range(2) for x in range(10) if i == x%2]
выходы
[0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
Ответ 4
Ли имеет ответ на синтаксический вопрос. Предложение: не так много вставляйте в тело генератора. Функция намного читаема.
def make_generator(modulus):
return (x for x in range(10) if x % 2 == modulus)
g = (make_generator(i) for i in range(2))