【问题标题】:How to split a Python generator of tuples into 2 separate generators?如何将元组的 Python 生成器拆分为 2 个单独的生成器?
【发布时间】:2015-01-19 17:14:01
【问题描述】:

我有一个生成器,大致如下:

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

从这个生成器中,我想创建 2 个单独的生成器,如下所示:

for a in gen1_split_a():
    yield a

for b in gen1_split_b():
    yield b

我的玩法是什么,SA?

【问题讨论】:

  • 你可以做for a, b in gen1():,但是如果你想要所有的as然后所有的bs,我建议完全重组;使用生成器没有意义,因为您需要预先获得所有对。
  • 您可以只运行生成器两次,每次只使用两个元素之一。
  • 为什么不只做gen = gen1()def gen_bs(gen): for b in gen: yield b[1]?,然后gen_bs(gen)...?

标签: python generator


【解决方案1】:

您不能,也不能最终保留所有生成器输出,以便能够在第二个循环中生成 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 产生的所有东西。来自文档:

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

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

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

for b in b_values:
    print b

或者更好的是,只需在一个循环中同时处理 ab

【讨论】:

  • 感谢马丁!这个前者成功了。也就是说,我做了一些重构,并从代码中完全省略了这一点。不过,下次保存在我的笔记中!
【解决方案2】:

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

它利用 Python 将值发送回生成器的能力来影响未来的产量。同样的想法也应该可以用类来实现,但我还是想掌握生成器。

当新的生成器初始化时,它们只知道当前的n-tuple。每次他们在各自的索引处产生值时,都会执行回调,通知更高级别的生成器该索引。一旦当前元组的所有索引都产生了,更高级别的生成器就会转到下一个元组并重复该过程。

这可能有点笨拙,但这里是代码(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 仅在当前元组中的所有值都已产生后才会进行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-01
    • 2013-10-02
    • 1970-01-01
    • 1970-01-01
    • 2016-01-21
    • 2010-12-28
    • 2022-11-11
    • 2012-11-13
    相关资源
    最近更新 更多