簡體   English   中英

如何在 Python 中加入兩個生成器?

[英]How to join two generators in 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()

我得到錯誤:

+ 不支持的操作數類型:“生成器”和“生成器”

如何在 Python 中加入兩個生成器?

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

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

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

簡單的例子:

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

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

這里它使用了一個帶有嵌套for s 的生成器表達式

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]

也可以使用解包運算符*

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

注意:對於“非惰性”迭代最有效。 也可以與不同類型的理解一起使用。 生成器 concat 的首選方法來自@Uduse的答案

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()是基本的。

如果您有可迭代的可迭代對象, itertools.chain.from_iterable()會很方便。 例如每個子目錄的文件列表,例如[ ["src/server.py", "src/readme.txt"], ["test/test.py"] ]

如果你想保持生成器分開但仍然同時迭代它們,你可以使用 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

(免責聲明:僅限 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), ...]

臨:

這種方法的好處是您不必導入任何東西,而且代碼也不多。

缺點:

缺點是您將兩個生成器轉儲到一個集合中,然后迭代該集合,有效地執行兩次傳遞並可能使用大量內存。

假設我們必須生成生成器(gen1 和 gen 2),並且我們想要執行一些需要兩者結果的額外計算。 我們可以通過 map 方法返回此類函數/計算的結果,該方法又返回一個我們可以循環的生成器。

在這種情況下,需要通過 lambda 函數來實現函數/計算。 棘手的部分是我們的目標是在地圖及其 lambda 函數中做些什么。

建議解決方案的一般形式:

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

我想說,正如用戶“wjandrea”在評論中所建議的那樣,最好的解決方案是

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

它不會改變返回的類型,並且是真正的 Pythonic。

您可以將任何生成器放入列表中。 雖然你不能組合生成器,但你可以組合列表。 這樣做的缺點是您實際上在 memory 中創建了 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) 

如果您想從 know 目錄之前和之后獲取文件路徑列表,您可以這樣做:

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')])

我知道有更好的答案,這是我覺得的簡單代碼。

如果您只需要執行一次並且不想再導入一個模塊,那么有一個簡單的解決方案......

做就是了:

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()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM