Wie verbinde ich zwei Generatoren in Python?

187

Ich möchte den folgenden Code ändern

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

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

zu diesem Code:

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

Ich bekomme den Fehler:

nicht unterstützte Operandentypen für +: 'Generator' und 'Generator'

Wie verbinde ich zwei Generatoren in Python?

Homer Xing
quelle
1
Ich möchte auch, dass Python so funktioniert. Habe genau den gleichen Fehler!
Adam Kurkiewicz

Antworten:

232

Ich denke itertools.chain()sollte es tun.

Philipp
quelle
5
Man sollte bedenken, dass der Rückgabewert von itertools.chain()keine types.GeneratorTypeInstanz zurückgibt. Nur für den Fall, dass der genaue Typ entscheidend ist.
Riga
1
warum schreibst du nicht auch ein ausgearbeitetes Beispiel auf?
Charlie Parker
74

Ein Beispiel für Code:

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
Cesio
quelle
10
Warum nicht dieses Beispiel zu der bereits vorhandenen, hoch bewerteten itertools.chain()Antwort hinzufügen ?
Jean-François Corbett
51

In Python (3.5 oder höher) können Sie Folgendes tun:

def concat(a, b):
    yield from a
    yield from b
Uduse
quelle
7
So viel Python.
Ramazan Polat
9
Allgemeiner: def chain(*iterables): for iterable in iterables: yield from iterable(Setzen Sie das defund forin separate Zeilen, wenn Sie es ausführen.)
wjandrea
Wird alles von a nachgegeben, bevor etwas von b nachgegeben wird, oder werden sie abgewechselt?
Problemoffizier
@ Problemofficer Yup. Nur awird geprüft, bis alles daraus ergibt, auch wenn bes sich nicht um einen Iterator handelt. Das, TypeErrorweil bich kein Iterator bin, wird später auftauchen.
GeeTransit
35

Einfaches Beispiel:

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
user1767754
quelle
3
Warum nicht dieses Beispiel zu der bereits vorhandenen, hoch bewerteten itertools.chain()Antwort hinzufügen ?
Jean-François Corbett
Dies ist nicht ganz richtig, da itertools.chainein Iterator zurückgegeben wird, kein Generator.
David J.
Kannst du nicht einfach tun chain([1, 2, 3], [3, 4, 5])?
Corman
10

Mit itertools.chain.from_iterable können Sie Folgendes tun:

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)
Andrew Pate
quelle
Sie verwenden ein unnötiges Listenverständnis. Sie verwenden auch einen unnötigen Generatorausdruck, gennywenn er bereits einen Generator zurückgibt. list(itertools.chain.from_iterable(genny(x)))ist viel prägnanter.
Corman
Das Verständnis war eine einfache Möglichkeit, die beiden Generatoren gemäß der Frage zu erstellen. Vielleicht ist meine Antwort in dieser Hinsicht etwas verworren.
Andrew Pate
Ich denke, der Grund, warum ich diese Antwort zu den bestehenden hinzugefügt habe, war, denen zu helfen, die zufällig viele Generatoren haben, mit denen sie umgehen müssen.
Andrew Pate
Es ist kein einfacher Weg, es gibt viele einfachere Wege. Die Verwendung von Generatorausdrücken in einem vorhandenen Generator verringert die Leistung, und der listKonstruktor ist viel besser lesbar als das Listenverständnis. Ihre Methode ist in dieser Hinsicht viel unleserlicher.
Corman
Corman, ich stimme zu, dass Ihr Listenkonstruktor tatsächlich besser lesbar ist. Es wäre jedoch gut, Ihre 'vielen einfacheren Wege' zu sehen ... Ich denke, der Kommentar von wjandrea oben scheint dasselbe zu tun wie itertools.chain.from_iterable. Es wäre gut, sie zu fahren und zu sehen, wer am schnellsten ist.
Andrew Pate
8

Hier wird ein Generatorausdruck mit verschachtelten fors verwendet:

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]
Alexey
quelle
2
Eine kleine Erklärung würde nicht schaden.
Ramazan Polat
Nun, ich glaube nicht, dass ich das besser erklären kann als Pythons Dokumentation.
Alexey
(Die Dokumentation für Generatorausdrücke ist mit meiner Antwort verknüpft. Ich sehe keinen guten Grund, die Dokumentation zu kopieren und in meine Antwort einzufügen.)
Alexey
3

Man kann auch den Unpack-Operator verwenden *:

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

HINWEIS: Funktioniert am effizientesten für nicht faule Iterables. Kann auch mit unterschiedlichen Verständnisweisen verwendet werden. Der bevorzugte Weg für Generator Concat wäre die Antwort von @Uduse

sol25
quelle
1

Wenn Sie die Generatoren getrennt halten und dennoch gleichzeitig durchlaufen möchten, können Sie zip () verwenden:

HINWEIS: Die Iteration stoppt am kürzeren der beiden Generatoren

Beispielsweise:

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
Geteilt durch Null
quelle
0

Nehmen wir an, wir müssen Generatoren (Gen1 und Gen2) und wir möchten eine zusätzliche Berechnung durchführen, die das Ergebnis von beiden erfordert. Wir können das Ergebnis einer solchen Funktion / Berechnung über die Map-Methode zurückgeben, die wiederum einen Generator zurückgibt, auf dem wir eine Schleife ausführen können.

In diesem Szenario muss die Funktion / Berechnung über die Lambda-Funktion implementiert werden. Der schwierige Teil ist das, was wir innerhalb der Karte und ihrer Lambda-Funktion erreichen wollen.

Allgemeine Form der vorgeschlagenen Lösung:

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

All diese komplizierten Lösungen ...

mach einfach:

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

Wenn Sie wirklich beide Generatoren "verbinden" möchten, gehen Sie wie folgt vor:

for directory, dirs, files in 
        [x for osw in [os.walk(director_1), os.walk(director_2)] 
               for x in osw]:
    do_something()
Camion
quelle