如何在Python中连接两个生成器?

我想更改下面的代码

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中连接两个生成器?

122483 次浏览

itertools.chain()应该这样做。它接受多个可迭代对象,并逐个产生结果,大致相当于:

def chain(*iterables):
for it in iterables:
for element in it:
yield element

使用的例子:

from itertools import chain


g = (c for c in 'ABC')  # Dummy generator, just for example
c = chain(g, 'DEF')  # Chain the generator and a string
for item in c:
print(item)

输出:

A
B
C
D
E
F

代码示例:

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

使用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)

简单的例子:

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

如果你想保持生成器的分离,但仍然在同一时间遍历它们,你可以使用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
假设我们有两个生成器(第1代和第2代),我们想要执行一些额外的计算,需要这两个生成器的结果。 我们可以通过map方法返回这种函数/计算的结果,该方法反过来返回一个生成器,我们可以循环使用它。< / p > 在这种情况下,函数/计算需要通过lambda函数来实现。 棘手的部分是我们的目标是在映射和它的lambda函数中做什么

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

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

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

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

这里它使用了带有嵌套__abc0的生成器表达式:

range_a = range(3)
range_b = range(5)
result = (item
for one_range in (range_a, range_b)
for item in one_range)
assert list(result) == [0, 1, 2, 0, 1, 2, 3, 4]

for ... in ...从左到右求值。for之后的标识符建立了一个新变量。虽然one_range在下面的for ... in ...中使用,但第二个赋值表达式中的item在“final”赋值表达式中使用,该赋值表达式只有一个(在最开始)。

如果你只需要做一次,不希望再导入一个模块,有一个简单的解决方案…

只做:

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

如果你真的想“加入”;两个生成器,然后做:

for directory, dirs, files in (
x for osw in [os.walk(directory_1), os.walk(directory_2)]
for x in osw
):
do_something()

还可以使用解包操作符*:

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

注意:对于“非惰性”迭代对象最有效。也可以用于不同类型的推导式。生成器连接的首选方式将来自@Uduse的答案

我想说的是,正如用户“wjandrea”的评论所建议的那样,最好的解决方案是

def concat_generators(*gens):
for gen in gens:
yield from gen

它不会改变返回的类型,并且是真正的python类型。

2020年更新:在Python 3和Python 2中都可以工作

import itertools


iterA = range(10,15)
iterB = range(15,20)
iterC = range(20,25)

第一个选项

for i in itertools.chain(iterA, iterB, iterC):
print(i)


# 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

可选选项,在python 2.6中引入

for i in itertools.chain.from_iterable( [iterA, iterB, iterC] ):
print(i)


# 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

itertools.chain ()是基本的。

如果你有一个iterables的iterable, itertools.chain.from_iterable ()很方便。例如,每个子目录的文件列表,如[ ["src/server.py", "src/readme.txt"], ["test/test.py"] ]

(免责声明:仅限Python 3 !)

与你想要的语法相似的是使用splat操作符展开两个生成器:

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

解释:

这有效地执行了将两个生成器的单层扁平化为3元组的n元组(来自os.walk),如下所示:

((directory1, dirs1, files1), (directory2, dirs2, files2), ...)

然后for循环遍历这个n元组。

当然,通过简单地用括号替换外括号,你可以得到一个3元组的列表,而不是3元组的n元组:

for directory, dirs, files in [*os.walk(directory_1), *os.walk(directory_2)]:
do_something()

结果如下:

[(directory1, dirs1, files1), (directory2, dirs2, files2), ...]

正方观点:

这种方法的优点是不需要导入任何东西,也不需要大量代码。

反对:

缺点是将两个生成器转储到一个集合中,然后遍历该集合,有效地进行了两次传递,可能会使用大量内存。

您可以将任何生成器放入列表中。虽然不能组合生成器,但可以组合列表。这样做的缺点是实际上在内存中创建了3个列表,但优点是可读性非常好,不需要导入,并且是单行习惯用法。

OP解决方案。

for directory, dirs, files in list(os.walk(directory_1)) + list(os.walk(directory_2)):
do_something()
a = range(20)
b = range(10,99,3)
for v in list(a) + list(b):
print(v)

如果你想从一个已知目录之前和之后获取文件路径列表,你可以这样做:

for r,d,f in os.walk(current_dir):
for dir in d:
if dir =='after':
after_dir = os.path.abspath(os.path.join(current_dir, dir))
for r,d,f in os.walk(after_dir):
after_flist.append([os.path.join(r,file)for file in f if file.endswith('json')])
                              

elif dir =='before':
before_dir = os.path.abspath(os.path.join(current_dir, dir))
for r,d,f in os.walk(before_dir):
before_flist.append([os.path.join(r,file)for file in f if file.endswith('json')])

我知道有更好的答案,这是我觉得简单的代码。