如何在Python中加入两个生成器?


187

我想更改以下代码

for directory, dirs, files in os.walk(directory_1):
    do_something()

for directory, dirs, files in os.walk(directory_2):
    do_something()

此代码:

for directory, dirs, files in os.walk(directory_1) + os.walk(directory_2):
    do_something()

我得到了错误:

+不支持的操作数类型:“ generator”和“ generator”

如何在Python中加入两个生成器?


1
我也希望Python以这种方式工作。得到了完全一样的错误!
亚当·库基维奇

Answers:


232

我认为itertools.chain()应该这样做。


5
请记住,的返回值itertools.chain()不会返回types.GeneratorType实例。以防万一,确切的类型至关重要。
里加

1
您为什么不还写下一个可行的示例?
查理·帕克

74

代码示例:

from itertools import chain

def generator1():
    for item in 'abcdef':
        yield item

def generator2():
    for item in '123456':
        yield item

generator3 = chain(generator1(), generator2())
for item in generator3:
    print item

10
为什么不将这个示例添加到已经存在的,高度评价的itertools.chain()答案中呢?
让·弗朗索瓦·科贝特

51

在Python(3.5或更高版本)中,您可以执行以下操作:

def concat(a, b):
    yield from a
    yield from b

7
这么多pythonic。
Ramazan Polat

9
更笼统:(在运行时def chain(*iterables): for iterable in iterables: yield from iterabledeffor放在单独的行上。)
wjandrea

一切都从一个从任何事情之前得到b是产生或正在被它们交替?
问题人员

@问题官Yup。a即使b不是迭代器,也只检查直到产生所有结果为止。该TypeErrorb不作为一个迭代器上来了以后。
GeeTransit

35

简单的例子:

from itertools import chain
x = iter([1,2,3])      #Create Generator Object (listiterator)
y = iter([3,4,5])      #another one
result = chain(x, y)   #Chained x and y

3
为什么不将这个示例添加到已经存在的,高度评价的itertools.chain()答案中呢?
让·弗朗索瓦·科贝特

这不是很正确,因为itertools.chain返回的是迭代器,而不是生成器。
David J.

你不能做chain([1, 2, 3], [3, 4, 5])吗?
Corman

10

使用itertools.chain.from_iterable,您可以执行以下操作:

def genny(start):
  for x in range(start, start+3):
    yield x

y = [1, 2]
ab = [o for o in itertools.chain.from_iterable(genny(x) for x in y)]
print(ab)

您正在使用不必要的列表理解。genny当已经返回一个生成器时,您还将使用一个不必要的生成器表达式。list(itertools.chain.from_iterable(genny(x)))更简洁。
Corman

根据这个问题,!ist理解是创建两个生成器的简单方法。在这方面,也许我的回答有些混乱。
安德鲁·帕特

我猜我将这个答案添加到现有答案中的原因是为了帮助那些碰巧有很多发电机要处理的人。
安德鲁·帕特

这不是一个简单的方法,有许多更简单的方法。在现有生成器上使用生成器表达式会降低性能,并且list构造函数比列表理解更具可读性。在这些方面,您的方法更加不可读。
Corman

Corman,我同意您的列表构造函数确实更具可读性。不过,很高兴看到您的“许多更简便的方法” ...我认为wjandrea的上述注释看起来与itertools.chain.from_iterable相同,因此最好与他们赛跑并看看谁最快。
安德鲁·帕特

8

在这里,它使用带有s 的生成器表达式for

a = range(3)
b = range(5)
ab = (i for it in (a, b) for i in it)
assert list(ab) == [0, 1, 2, 0, 1, 2, 3, 4]

2
稍加解释就不会受伤。
拉马赞(Ramazan Polat)

好吧,我认为我不能比Python的文档更好地解释这一点。
阿列克谢

(生成器表达式的文档是从我的答案中链接的。我看不出将文档复制并粘贴到我的答案中的充分理由。)
Alexey,

3

也可以使用unpack运算符*

concat = (*gen1(), *gen2())

注意:对于“非惰性”可迭代对象,工作效率最高。也可以与其他种类的理解一起使用。生成器concat的首选方法是来自@Uduse的答案


1

如果要使生成器分开,但仍要同时遍历它们,则可以使用zip():

注意:迭代在两个生成器中的较短者处停止

例如:

for (root1, dir1, files1), (root2, dir2, files2) in zip(os.walk(path1), os.walk(path2)):

    for file in files1:
        #do something with first list of files

    for file in files2:
        #do something with second list of files

0

假设我们必须使用生成器(gen1和gen 2),并且我们想要执行一些额外的计算,这需要两者的结果。我们可以通过map方法返回这种函数/计算的结果,而map方法又返回我们可以循环使用的生成器。

在这种情况下,需要通过lambda函数来实现函数/计算。棘手的部分是我们打算在地图及其lambda函数中进行的操作。

建议解决方案的一般形式:

def function(gen1,gen2):
        for item in map(lambda x, y: do_somethin(x,y), gen1, gen2):
            yield item

0

所有那些复杂的解决方案...

做就是了:

for dir in director_1, directory_2:
    for directory, dirs, files in os.walk(dir):
        do_something()

如果您确实要“加入”两个生成器,请执行以下操作:

for directory, dirs, files in 
        [x for osw in [os.walk(director_1), os.walk(director_2)] 
               for x in osw]:
    do_something()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.