Jak połączyć dwa generatory w Pythonie?

187

Chcę zmienić następujący kod

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

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

do tego kodu:

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

Dostaję błąd:

nieobsługiwane typy operandów dla +: „generator” i „generator”

Jak połączyć dwa generatory w Pythonie?

Homer Xing
źródło
1
Chciałbym również, aby Python działał w ten sposób. Wystąpił dokładnie ten sam błąd!
Adam Kurkiewicz

Odpowiedzi:

235

Myślę, że itertools.chain()powinienem to zrobić.

Philipp
źródło
5
Należy pamiętać, że zwracana wartość parametru itertools.chain()nie zwraca types.GeneratorTypeinstancji. Na wszelki wypadek dokładny typ ma kluczowe znaczenie.
Ryga
1
dlaczego nie zapisujesz również wypracowanego przykładu?
Charlie Parker
75

Przykład kodu:

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
źródło
10
Dlaczego nie dodać tego przykładu do już istniejącej, wysoko cenionej itertools.chain()odpowiedzi?
Jean-François Corbett
51

W Pythonie (3.5 lub nowszym) możesz:

def concat(a, b):
    yield from a
    yield from b
Uduse
źródło
7
Tyle pytonów.
Ramazan Polat
9
Bardziej ogólna: def chain(*iterables): for iterable in iterables: yield from iterable( Po uruchomieniu deffor
umieść
Czy wszystko jest uzyskiwane przed uzyskaniem czegokolwiek z b, czy też są na przemian?
problemofficer
@problemofficer Yup. aSprawdzane jest tylko, dopóki wszystko z niego bnie zostanie wydobyte , nawet jeśli nie jest iteratorem. To, TypeErrorże bnie jest iteratorem, pojawi się później.
GeeTransit
36

Prosty przykład:

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
użytkownik1767754
źródło
3
Dlaczego nie dodać tego przykładu do już istniejącej, wysoko cenionej itertools.chain()odpowiedzi?
Jean-François Corbett
To nie do końca dobrze, ponieważ itertools.chainzwraca iterator, a nie generator.
David J.
Nie możesz po prostu zrobić chain([1, 2, 3], [3, 4, 5])?
Corman
10

Dzięki itertools.chain.from_iterable możesz wykonywać następujące czynności:

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)
Andrzej Pasztet
źródło
Używasz niepotrzebnego zrozumienia listy. Używasz również niepotrzebnego wyrażenia generatora, gennygdy już zwraca generator. list(itertools.chain.from_iterable(genny(x)))jest o wiele bardziej zwięzłe.
Corman
Zrozumienie! Ist było łatwym sposobem na utworzenie dwóch generatorów, zgodnie z pytaniem. Może moja odpowiedź jest pod tym względem nieco skomplikowana.
andrew pasztet
Wydaje mi się, że powodem, dla którego dodałem tę odpowiedź do istniejących, była pomoc tym, którym akurat było mnóstwo generatorów.
andrew pasztet
To nie jest łatwy sposób, istnieje wiele łatwiejszych sposobów. Użycie wyrażeń generatora na istniejącym generatorze obniży wydajność, a listkonstruktor będzie znacznie bardziej czytelny niż zrozumienie listy. Pod tym względem twoja metoda jest znacznie bardziej nieczytelna.
Corman
Corman, zgadzam się, że twój konstruktor listy jest rzeczywiście bardziej czytelny. Dobrze byłoby zobaczyć „wiele łatwiejszych sposobów” ... Myślę, że powyższy komentarz wjandrea wygląda tak samo, jak itertools.chain.from_iterable, dobrze byłoby ścigać się z nimi i zobaczyć, kto najszybciej.
andrew pasztet
8

Tutaj używa wyrażenia generatorowego z zagnieżdżonymi fors:

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
źródło
2
Małe wyjaśnienie nie zaszkodzi.
Ramazan Polat
Cóż, nie sądzę, żebym mógł to wyjaśnić lepiej niż dokumentacja Pythona.
Alexey,
(Dokumentacja wyrażeń generatora jest połączona z moją odpowiedzią. Nie widzę dobrego powodu, aby kopiować i wklejać dokumentację do mojej odpowiedzi.)
Alexey
3

Można również użyć operatora rozpakowania *:

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

UWAGA: Działa najbardziej wydajnie w przypadku iteratorów „nie leniwych”. Może być również używany z różnymi rodzajami rozumienia. Preferowanym sposobem dla konkat generatora będzie odpowiedź z @Uduse

sol25
źródło
1

Jeśli chcesz oddzielić generatory, ale jednocześnie iterować nad nimi, możesz użyć zip ():

UWAGA: Iteracja zatrzymuje się na krótszym z dwóch generatorów

Na przykład:

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
Dzielony przez zero
źródło
0

Powiedzmy, że musimy generatory (gen1 i gen 2) i chcemy wykonać dodatkowe obliczenia, które wymagają wyniku obu. Możemy zwrócić wynik takiej funkcji / obliczenia za pomocą metody map, która z kolei zwraca generator, który możemy zapętlić.

W tym scenariuszu funkcję / obliczenia należy zaimplementować za pomocą funkcji lambda. Najtrudniejszą częścią jest to, co chcemy zrobić wewnątrz mapy i jej funkcji lambda.

Ogólna forma proponowanego rozwiązania:

def function(gen1,gen2):
        for item in map(lambda x, y: do_somethin(x,y), gen1, gen2):
            yield item
Mahdi Ghelichi
źródło
0

Wszystkie te skomplikowane rozwiązania ...

po prostu zrób:

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

Jeśli naprawdę chcesz „połączyć” oba generatory, wykonaj następujące czynności:

for directory, dirs, files in 
        [x for osw in [os.walk(director_1), os.walk(director_2)] 
               for x in osw]:
    do_something()
Camion
źródło
0

Powiedziałbym, że jak sugeruje w komentarzach użytkownika „wjandrea”, najlepszym rozwiązaniem jest

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

Nie zmienia zwracanego typu i jest naprawdę pythoniczny.

Luca Di Liello
źródło