如何将元组的Python生成器拆分为2个独立的生成器?

时间:2015-01-19 17:14:02

标签: python generator

我的生成器大致如下:

def gen1():
    for x, y in enumerate(xrange(20)):
        a = 5*x
        b = 10*y
        yield a, b

从这台发电机,我想创建两个独立的发电机,如下所示:

for a in gen1_split_a():
    yield a

for b in gen1_split_b():
    yield b

我的戏是什么,SA?

2 个答案:

答案 0 :(得分:9)

你不能,不能最终保持所有发生器输出只是为了能够在第二个循环中产生b值。在内存方面,这可能会花费很多。

您使用itertools.tee()来“复制”生成器:

from itertools import tee

def split_gen(gen):
    gen_a, gen_b = tee(gen, 2)
    return (a for a, b in gen_a), (b for a, b in gen_b)

gen1_split_a, gen1_split_b = split_gen(gen1)

for a in gen1_split_a:
    print a

for b in gen1_split_b:
    print b

但在这种情况下会发生的情况是tee对象最终必须存储所有gen1生成的。来自文档:

  

此itertool可能需要大量辅助存储(取决于需要存储多少临时数据)。通常,如果一个迭代器在另一个迭代器启动之前使用了大部分或全部数据,则使用list()代替tee()会更快。

遵循该建议,只需将b值放入第二个循环的列表中:

b_values = []
for a, b in gen1():
    print a
    b_values.append(a)

for b in b_values:
    print b

或更好,只需在一个循环中处理ab

答案 1 :(得分:0)

我有一个解决方案可能并不完全是您想要的。它将n元组生成器分离为n个独立生成器的元组。 但是,它要求返回当前元组的每个单独的值才能继续进行下一个元组。严格来说,它会将n元组生成器“拆分”为n生成器,但是您的示例无法按所示进行。

它利用Python将值发送回生成器以影响未来收益的能力。同样的想法也应该可以通过类来实现,但是无论如何我都想抓住生成器。

初始化新生成器时,它们仅知道当前的n元组。每次他们在各自的索引处产生值时,都会执行回调,以将该索引通知上级生成器。产生了当前元组的所有索引后,更高级别的生成器将移至下一个元组,然后重复该过程。

可能有点笨拙,但这是代码(Python 3.6)。

from typing import TypeVar, Generator, Tuple, Iterator, Optional

TYPE_A = TypeVar("TYPE_A")


def _next_value(source: Iterator[Tuple[TYPE_A, ...]], size: int) -> Generator[Tuple[TYPE_A, ...], Optional[int], None]:
    checked = [False for _ in range(size)]
    value = next(source)
    while True:
        index = yield value
        if all(checked):
            value = next(source)
            for _i in range(len(checked)):
                checked[_i] = False
        checked[index] = True


def _sub_iterator(index: int, callback: Generator[Tuple[TYPE_A, ...], int, None]) -> Generator[TYPE_A, None, None]:
    while True:
        value = callback.send(index)
        yield value[index]


def split_iterator(source: Iterator[Tuple[TYPE_A, ...]], size: int) -> Tuple[Generator[TYPE_A, Optional[TYPE_A], None], ...]:
    generators = []

    _cb = _next_value(source, size)
    _cb.send(None)

    for _i in range(size):
        each_generator = _sub_iterator(_i, _cb)
        generators.append(each_generator)

    return tuple(generators)


if __name__ == "__main__":
    def triple():
        _i = 0
        while True:
            yield tuple(range(_i, _i + 3))
            _i += 1

    g = triple()
    for i, each_value in enumerate(g):
        if i >= 5:
            break
        print(each_value)

    print()

    g = triple()
    a_gen, b_gen, c_gen = split_iterator(g, 3)
    for i, (a_value, b_value, c_value) in enumerate(zip(a_gen, b_gen, c_gen)):
        if i >= 5:
            break
        print((a_value, b_value, c_value))

triple()是一个三元组生成器,split_iterator()生成三个生成器,每个生成器从triple()生成的元组中产生一个索引。每个_sub_iterator仅在获得了当前元组的所有值后才进行处理。