Расширенный синтаксис понимания вложенных списков - PullRequest
41 голосов
/ 22 сентября 2010

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

Я пытался написать генератор, который генерировал генераторы. Простой генератор, использующий понимание списка, будет выглядеть так:

(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: читая вопрос, я понял, что это немного похоже на домашнее задание - это не так.

Ответы [ 4 ]

39 голосов
/ 22 сентября 2010

вам нужно использовать несколько скобок:

((x for x in range(10) if x%2==i) for i in range(2))

Для меня это не имело смысла, поэтому я подумал, что лучше сначала попробовать что-нибудь попроще.Поэтому я вернулся к спискам и попытался:

[>>> [x для x в диапазоне (10), если x% 2 == i для i в диапазоне (2)] [1, 1, 3,3, 5, 5, 7, 7, 9, 9.Попробуйте запустить новый интерпретатор Python, и это не удастся из-за NameError.Текущее поведение счетчика было удалено в Python 3.

РЕДАКТИРОВАТЬ:

Эквивалент для цикла для:

(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)
8 голосов
/ 05 февраля 2013

Эквивалентный цикл Ли Райана приводит меня к следующему, которое, кажется, работает просто отлично:

[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]
7 голосов
/ 22 сентября 2010

Расширяя немного ответ Ли Райана:

что-то = (x для x в диапазоне (10), если x% 2 == i для i в диапазоне (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 голосов
/ 22 сентября 2010

Ложь имеет ответ на синтаксический вопрос.Подсказка: не кладите так много в корпус генератора.Функция гораздо более читабельна.

def make_generator(modulus):
    return (x for x in range(10) if x % 2 == modulus)
g = (make_generator(i) for i in range(2))
...