Вложенные генераторы Python
Я пытался реализовать обратную функцию 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 использует последнее значение, поэтому генераторы не сохраняют значение, пока они создаются внутри первого генератора.
Мне чего-то не хватает, и я не знаю, чего именно.
Заранее спасибо, если что-то может прояснить мне эту ситуацию.
Обновление:
Я нашел объяснение здесь PEP-289, мое первое чтение было в PEP-255.
Решение, которое я пытаюсь реализовать, является ленивым, поэтому:
zip(*iter) or izip(*...)
Не работает для меня, потому что *arg расширяет список аргументов.
2 ответов:
Вы изобретаете колесо сумасшедшим способом.
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не изменяется.
Ладно, это немного сложно. Когда вы используете имя, подобное
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)
Comments