Python中如何逐次分别迭代多个生成器

我想先从第一个生成器取第 1 个值,从第二个生成器取第 1 个值,从第三个生成器取第 1 个值

接下来从第一个生成器取第 2 个值,从第二个生成器取第 2 个值,从第三个生成器取第 2 个值

以此类推,

最后从第一个生成器取第 5 个值,从第二个生成器取第 5 个值,从第三个生成器取第 5 个值

a = (x for x in range(1, 6))
b = (x for x in range(6, 11))
c = (x for x in range(11, 16))

d = (x for x in [a, b, c])

def y(): for m in d: yield next(m) for i in y(): print(i)

我想实现的输出为:

1
6
11
2
7
12
3
8
13
4
9
14
5
10
15

但是,上面的代码由于生成器只能被完整迭代一次所以在 for m in d:这个位置就会出问题。最后只能得到 1, 6, 11

请问有什么比较好的办法解决实现这个需求吗?

这个问题,是为了实现逐行对比超大 Log。我想一行一行对比 Log,但是由于三个 Log 各自都超过了 40G,因此想通过生成器的这种方式来实现。


Python中如何逐次分别迭代多个生成器

26 回复

a = range(1,6)
b = range(6,11)
c = range(11,16)

def y():
----while True:
--------yield a.next()
--------yield b.next()
--------yield c.next()

for i in y():
----print(i)


在Python里同时迭代多个生成器,用zip()最直接,但要注意它会在最短的生成器耗尽时停止。如果想让所有生成器都跑完,可以用itertools.zip_longest()

from itertools import zip_longest

def gen1():
    yield from range(3)
def gen2():
    yield from ['a', 'b', 'c', 'd']

# 用zip,短的停就全停
for a, b in zip(gen1(), gen2()):
    print(a, b)
# 输出:
# 0 a
# 1 b
# 2 c

# 用zip_longest,长的继续,短的补None
for a, b in zip_longest(gen1(), gen2()):
    print(a, b)
# 输出:
# 0 a
# 1 b
# 2 c
# None d

如果生成器数量不确定,或者想更手动地控制,可以用next()加循环处理StopIteration

def iterate_multiple_gens(*generators):
    iters = [iter(g) for g in generators]
    while True:
        values = []
        for it in iters:
            try:
                values.append(next(it))
            except StopIteration:
                values.append(None)  # 或者用其他哨兵值
        if all(v is None for v in values):
            break
        yield tuple(values)

# 使用示例
for vals in iterate_multiple_gens(gen1(), gen2()):
    print(vals)

简单说,根据你是要“对齐”迭代还是“独立”迭代到各自结束,选zip或自己写循环。

可以正常运行,非常感谢。

但如果有非常多的文件呢,比如有一万个文件,那在 while True 没有办法分别单独 yield xxx.next()了。此时又怎么办?

In [21]: a = iter(range(1,6))
…: b = iter(range(6,11))
…: c = iter(range(11,16))
…:
…:
…:
…:

In [22]: def y():
…: while 1:
…: yield next(a)
…: yield next(b)
…: yield next©
…:

In [23]:

In [23]: for i in y():
…: print(i)
…:
1
6
11
2
7
12
3
8
13
4
9
14
5
10
15

>>> a = range(1,6)
>>> b = range(6,11)
>>> c = range(11,16)
>>>
>>> d = [a.iter(),b.iter(),c.iter()]
>>>
>>> def y():
… while True:
… for m in d:
… yield next(m)

>>> for i in y():
… print(i)

为什么我的显示 -。-AttributeError: ‘range’ object has no attribute ‘next

a = range(1,6)
b = range(6,11)
c = range(11,16)

d = [iter(a),iter(b),iter©]

def y():
----while True:
--------for m in d:
------------yield next(m)

for i in y():
----print(i)

➜ /tmp python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type “help”, “copyright”, “credits” or “license” for more information.
>>> ia = ( x for x in range (10) )
>>> ib = ( x for x in range(10) )
>>> ii = zip(ia,ib)
>>> for a,b in ii:
… print(a,b)

0 0
1 1


python3 里的 zip 出来的结果本身就是一个迭代器了

因为我忘记先调用 iter()了

其实你这段代码的前两行并不用把 range 对象转换成 tuple 的,直接 zip()即可,这样能大幅度减少内存占用

>>> ii = zip(range(1,6),range(6,11),range(11,16))
>>> i = iter(ii)
>>> next(i)
(1, 6, 11)
>>> next(i)
(2, 7, 12)
>>> next(i)
(3, 8, 13)
>>> next(i)
(4, 9, 14)
>>> next(i)
(5, 10, 15)
>>> next(i)
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
StopIteration

不会转成 tuple,python3 里这个看似 tuple 的东西还是个迭代器。。

换个思路, 像这样:

f1 = 'file1’
f2 = 'file2’
f3 = 'file3’


def do_sth(l1, l2, l3):
…pass


with open(f1, ‘r’) as fh1, open(f2, ‘r’) as fh2, open(f3, ‘r’) as fh3:
…while True:
… f1_line = fh1.readline()
…f2_line = fh2.readline()
…f3_line = fh3.readline()
…do_sth(f1_line, f2_line, f3_line)
…if not f1_line or not f2_line or not f3_line:
…break

>>> def getwhat():
… a = ( x for x in range(1,10))
… b = ( x for x in range(15,24))
… c = zip(a,b)
… for v in c:
… for vv in v:
… yield vv

>>> [b for b in getwhat()]
[1, 15, 2, 16, 3, 17, 4, 18, 5, 19, 6, 20, 7, 21, 8, 22, 9, 23]
这样可以每次只返回一个

好吧,还真的是,不过这样两层迭代器嵌套也没啥意思。。
>>> ( x for x in range (10) )
<generator object <genexpr> at 0x000001F09127FFC0>

准确说应该是一个生成器而不是迭代器

readline()就是逐行读的, 下次调用读的是下一行, 所以可以完美解决你这个问题

#!usr/bin/env python2.7
from itertools import izip

a = (x for x in range(1, 6))
b = (x for x in range(6, 11))
c = (x for x in range(11, 16))

ii = izip(a, b, c)


def func():
for i, (x, y, z) in enumerate(ii):
yield x
yield y
yield z


for item in func():
print item




izip 返回的对象是个迭代器,你看看是否合适。

itertools 是你的好朋友
https://docs.python.org/2/library/itertools.html

用里面叫 roundrobin 的函数

代码贴丑了,抱歉。

PS:萌新求教,如何正确的贴代码并且不影响代码缩进问题的呀?

iter(生成器)这样写的话,生成器里面的东西会被提前放出来吗?

在 Py3 中如果你写成(x for x in range (10)) 则不会,如果写成[x for x in range (10)]就会

可以用 yield from 语句

发现我在#24 楼说错了,yield from 会从生成器中挨个提取完才发挥

iter 函数调用对象的__iter__()方法; generator 是 iterator 的子类,iterator 要求实现__iter__()方法,并返回自身。所以 iter(生成器) 实际上直接返回了生成器。

另外,在#18 的基础上,如果生成器长度不同,且生成器中没有 None,可以用 filter 进行处理:

python3<br>def another_roundrobin(*iterables):<br> for i in itertools.zip_longest(*iterables, fillvalue=None):<br> yield from filter(lambda x: x is not None, i)<br>

PS:itertools 文档中,Itertools Recipes 章节的 roundrobin 函数写得非常巧妙

回到顶部