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.