Come unire due generatori in Python?


188

Voglio cambiare il seguente codice

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

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

a questo codice:

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

Ottengo l'errore:

tipi di operando non supportati per +: "generatore" e "generatore"

Come unire due generatori in Python?


1
Vorrei anche che Python funzionasse in questo modo. Hai esattamente lo stesso errore!
Adam Kurkiewicz,

Risposte:


236

Penso che itertools.chain()dovrebbe farlo.


5
Si dovrebbe tenere presente che il valore restituito di itertools.chain()non restituisce types.GeneratorTypeun'istanza. Nel caso in cui il tipo esatto sia cruciale.
Riga,

1
perché non scrivi anche un esempio elaborato?
Charlie Parker,

75

Un esempio di codice:

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
Perché non aggiungere questo esempio alla itertools.chain()risposta già esistente e altamente votata ?
Jean-François Corbett,

51

In Python (3.5 o versioni successive) puoi fare:

def concat(a, b):
    yield from a
    yield from b

7
Tanto pitone.
Ramazan Polat,

9
Più generale: def chain(*iterables): for iterable in iterables: yield from iterable(Metti il defe forsu linee separate quando lo esegui.)
wjandrea

Tutto viene da un ceduto prima che qualcosa da b sia ceduto o vengono alternati?
problemofficer,

@problemofficer Yup. Solo aè controllato fino a quando tutto è prodotto da esso, anche se bnon è un iteratore. Il TypeErrorper bnon essere un iteratore verrà fuori più tardi.
GeeTransit

36

Esempio semplice:

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
Perché non aggiungere questo esempio alla itertools.chain()risposta già esistente e altamente votata ?
Jean-François Corbett,

Questo non è del tutto corretto, poiché itertools.chainrestituisce un iteratore, non un generatore.
David J.

Non puoi farlo chain([1, 2, 3], [3, 4, 5])?
Corman,

10

Con itertools.chain.from_iterable puoi fare cose come:

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)

Stai utilizzando una comprensione dell'elenco non necessaria. Stai anche usando un'espressione di generatore non necessaria su gennyquando restituisce già un generatore. list(itertools.chain.from_iterable(genny(x)))è molto più conciso.
Corman,

La comprensione! Ist era un modo semplice per creare i due generatori, come da domanda. Forse la mia risposta è un po 'contorta in questo senso.
Andrew Pate,

Immagino che il motivo per cui ho aggiunto questa risposta a quelle esistenti sia stato quello di aiutare coloro che hanno molti generatori da gestire.
Andrew Pate,

Non è un modo semplice, ci sono molti modi più semplici. L'uso delle espressioni del generatore su un generatore esistente riduce le prestazioni e il listcostruttore è molto più leggibile della comprensione dell'elenco. Il tuo metodo è molto più illeggibile sotto questi aspetti.
Corman,

Corman, sono d'accordo che il costruttore della tua lista sia davvero più leggibile. Sarebbe bello vedere i tuoi "molti modi più semplici" però ... Penso che il commento di wjandrea sopra sembra fare lo stesso di itertools.chain.from_iterable sarebbe bello correre su di loro e vedere chi è più veloce.
Andrew Pate,

8

Qui sta usando un'espressione di generatore con fors nidificati :

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
Una piccola spiegazione non farebbe male.
Ramazan Polat,

Bene, non credo di poterlo spiegare meglio della documentazione di Python.
Alexey,

(La documentazione per le espressioni del generatore è collegata dalla mia risposta. Non vedo un buon motivo per copiare e incollare la documentazione nella mia risposta.)
Alexey

3

Si può anche usare l'operatore di decompressione *:

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

NOTA: funziona in modo più efficiente per iterabili "non pigri". Può anche essere usato con diversi tipi di comprensione. Il modo preferito per il concat generatore sarebbe dalla risposta di @Uduse


1

Se vuoi mantenere separati i generatori, ma continua a iterarli contemporaneamente puoi usare zip ():

NOTA: L'iterazione si arresta sul più corto dei due generatori

Per esempio:

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

Diciamo che dobbiamo generatori (gen1 e gen 2) e vogliamo eseguire alcuni calcoli extra che richiedono il risultato di entrambi. Possiamo restituire il risultato di tale funzione / calcolo attraverso il metodo della mappa, che a sua volta restituisce un generatore su cui possiamo eseguire il loop.

In questo scenario, la funzione / calcolo deve essere implementata tramite la funzione lambda. La parte difficile è ciò che miriamo a fare all'interno della mappa e la sua funzione lambda.

Forma generale della soluzione proposta:

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

0

Tutte quelle soluzioni complicate ...

basta fare:

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

Se vuoi davvero "unire" entrambi i generatori, allora fai:

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

0

Direi che, come suggerito nei commenti dell'utente "wjandrea", la soluzione migliore è

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

Non cambia il tipo restituito ed è veramente pythonic.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.