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?


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ć.


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

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

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

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)

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]

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


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

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

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

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.