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?


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.


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

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

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

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)

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]

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


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

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

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()
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.