Python вложенные генераторы - PullRequest
7 голосов
/ 22 июня 2011

Я пытался реализовать обратную функцию itertools.izip на Python 2.7.1. Дело в том, что я нахожу проблему, и у меня нет объяснения. Решение 1, iunzip_v1 работает отлично. Но решение 2. iunzip_v2 не работает должным образом. До сих пор я не нашел соответствующей информации об этой проблеме, и, читая PEP о генераторах, кажется, что он должен работать, но это не так.

import itertools
from operator import itemgetter

def iunzip_v1(iterable):
    _tmp, iterable = itertools.tee(iterable, 2)
    iters = itertools.tee(iterable, len(_tmp.next()))
    return tuple(itertools.imap(itemgetter(i), it) for i, it in enumerate(iters))

def iunzip_v2(iterable):
    _tmp, iterable = itertools.tee(iterable, 2)
    iters = itertools.tee(iterable, len(_tmp.next()))
    return tuple((elem[i] for elem in it) for i, it in enumerate(iters))

результат:

In [17]: l
Out[17]: [(0, 0, 0), (1, 2, 3), (2, 4, 6), (3, 6, 9), (4, 8, 12)]

In [18]: map(list, iunzip.iunzip_v1(l))
Out[18]: [[0, 1, 2, 3, 4], [0, 2, 4, 6, 8], [0, 3, 6, 9, 12]]

In [19]: map(list, iunzip.iunzip_v2(l))
Out[19]: [[0, 3, 6, 9, 12], [0, 3, 6, 9, 12], [0, 3, 6, 9, 12]]

Кажется, что iunzip_v2 использует последнее значение, поэтому генераторы не сохраняют значение, пока они создаются внутри первого генератора. Я что-то упускаю и не знаю, что это такое.

Заранее спасибо, если что-то прояснит мне эту ситуацию.

UPDATE: Я нашел объяснение здесь PEP-289 , мое первое чтение было на PEP-255. Решение, которое я пытаюсь реализовать, является ленивым, поэтому:

  zip(*iter) or izip(*...)

не работает для меня, потому что * arg расширяет список аргументов.

Ответы [ 2 ]

7 голосов
/ 22 июня 2011

Вы изобретаете колесо сумасшедшим способом.izip является его собственным обратным:

>>> list(izip(*izip(range(10), range(10))))
[(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)]

Но это не совсем отвечает на ваш вопрос, не так ли?

Проблема с вашими вложенными генераторами - это проблема с областью видимости, которая возникает из-засамые внутренние генераторы не используются до тех пор, пока самый внешний генератор не запустится:

def iunzip_v2(iterable):
    _tmp, iterable = itertools.tee(iterable, 2)
    iters = itertools.tee(iterable, len(_tmp.next()))
    return tuple((elem[i] for elem in it) for i, it in enumerate(iters))

Здесь вы генерируете три генератора, каждый из которых использует одну и ту же переменную , i.Копии этой переменной не сделаны.Затем tuple исчерпывает самый внешний генератор, создавая набор генераторов:

>>> iunzip_v2((range(3), range(3)))
(<generator object <genexpr> at 0x1004d4a50>, <generator object <genexpr> at 0x1004d4aa0>, <generator object <genexpr> at 0x1004d4af0>)

В этот момент каждый из этих генераторов выполнит elem[i] для каждого элемента it.А поскольку i теперь равно 3 для всех трех генераторов, вы каждый раз получаете последний элемент.

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

5 голосов
/ 22 июня 2011

Хорошо, это немного сложно.Когда вы используете имя типа i, значение, которое оно обозначает, ищется только во время выполнения.В этом коде:

return tuple((elem[i] for elem in it) for i, it in enumerate(iters))

вы возвращаете количество генераторов (elem[i] for elem in it), и каждый из них использует одно и то же имя i.Когда функция возвращается, цикл в tuple( .. for i in .. ) завершился, и i было установлено его окончательное значение (3 в вашем примере).Как только вы оцените эти генераторы в списки, все они создадут одинаковые значения, потому что они используют одинаковые i.

Кстати:

unzip = lambda zipped: zip(*zipped) 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...