Вложенные генераторы 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 расширяет список аргументов.

548   2  

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

    Ничего не найдено.