Pythonで2つのジェネレーターを結合する方法は?


188

次のコードを変更したい

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で2つのジェネレーターを結合する方法は?


1
また、Pythonをこのように動作させたいです。まったく同じエラーが発生しました!
Adam Kurkiewicz

回答:



75

コードの例:

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。
ラマザンポラット2018年

9
より一般的な:def chain(*iterables): for iterable in iterables: yield from iterable(置くdeffor。あなたはそれを実行したときに別の行に)
wjandrea

bから何かが生成される前に生成されものすべてですか、それとも代替されていますか?
problemofficer

@problemofficerうん。唯一のaすべてがそこから得られたされるまではあっても、チェックされているbイテレータではありません。TypeError以下のためのbイテレータされていないが、後に出てくるだろう。
GeeTransit

36

簡単な例:

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])か?
19:41のコーマン

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)))はるかに簡潔です。
コーマン

質問に従って、!istの理解は2つのジェネレータを作成する簡単な方法でした。多分私の答えはその点で少し複雑です。
アンドリューパテ

この答えを既存のものに追加したのは、たまたまジェネレーターがたくさんある人が対処するのを助けるためだったと思います。
アンドリューパテ

簡単な方法ではありません。簡単な方法はたくさんあります。既存のジェネレーターでジェネレーター式を使用するとパフォーマンスが低下し、listコンストラクターはリスト内包よりはるかに読みやすくなります。あなたの方法はそれらの点ではるかに読みにくいです。
コーマン

コーマン、私はあなたのリストコンストラクターが確かに読みやすいことに同意します。あなたの「多くのより簡単な方法」を見るのは良いことですが...上記の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
少し説明しても問題ありません。
ラマザン

まあ、私はこれをPythonのドキュメントよりもうまく説明できるとは思いません。
アレクセイ

(ジェネレータ式のドキュメントは私の回答からリンクされています。ドキュメントをコピーして私の回答に貼り付ける理由はわかりません。)
Alexey

3

アンパック演算子を使用することもできます*

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

注:「非レイジー」イテラブルに対して最も効率的に機能します。さまざまな種類の理解度で使用することもできます。ジェネレーター連結の好ましい方法は、@ Uduseからの回答です。


1

ジェネレータを個別に保ちながら、同時にそれらを繰り返し処理したい場合は、zip()を使用できます。

注:反復は2つのジェネレーターの短い方で停止します

例えば:

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メソッドは、ループ可能なジェネレータを返します。

このシナリオでは、関数/計算はラムダ関数を介して実装する必要があります。トリッキーな部分は、マップとそのラムダ関数の内部で行うことを目的とするものです。

提案されたソリューションの一般的な形式:

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

0

ユーザー「wjandrea」のコメントで示唆されているように、最良の解決策は

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

返されるタイプは変更されず、実際にはpythonicです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.