Как распаковать итератор?

Учитывая список пар xys, идиома Python для распаковки его в два списка:

xs, ys = zip(*xys)

Если xys - итератор, как я могу разархивировать его на два итератора, не сохраняя все в памяти?

Ответ 1

Предположим, что у вас есть несколько итераций пар:

a = zip(range(10), range(10))

Если я правильно интерпретирую то, о чем вы просите, вы можете генерировать независимые итераторы для первых и секунд, используя itertools.tee:

 xs, ys = itertools.tee(a)
 xs, ys = (x[0] for x in xs), (y[1] for y in ys)

Примечание, это сохранит в памяти "разницу" между тем, сколько вы повторяете одно из них, и другое.

Ответ 2

Если вы хотите потреблять один итератор независимо друг от друга, нет никакого способа избежать потащить материал в память, поскольку один из итераторов будет прогрессировать, пока другой не будет (и, следовательно, должен буферизировать).

Что-то вроде этого позволяет вам перебирать как "левые элементы", так и "правильные элементы" пар:

 import itertools
 import operator

 it1, it2 = itertools.tee(xys)
 xs = map(operator.itemgetter(0), it1))
 ys = map(operator.itemgetter(1), it2))

 print(next(xs))
 print(next(ys))

... но имейте в виду, что если вы потребляете только один итератор, другой будет хранить элементы в памяти до тех пор, пока вы не начнете их употреблять.

(Btw, предполагая Python 3. В Python 2 вам нужно использовать itertools.imap(), а не map().)

Ответ 3

Полный ответ находится здесь. Короче говоря: мы можем изменить рецепт Python для функции itertools.tee например:

from collections import deque


def unzip(iterable):
    """
    Transposes given iterable of finite iterables.
    """
    iterator = iter(iterable)
    try:
        first_elements = next(iterator)
    except StopIteration:
        return ()
    queues = [deque([element])
              for element in first_elements]

    def coordinate(queue):
        while True:
            if not queue:
                try:
                    elements = next(iterator)
                except StopIteration:
                    return
                for sub_queue, element in zip(queues, elements):
                    sub_queue.append(element)
            yield queue.popleft()

    return tuple(map(coordinate, queues))

а затем использовать его

>>> from itertools import count
>>> zipped = zip(count(), count())
>>> xs, ys = unzip(zipped)
>>> next(xs)
0