¿Cómo unir dos generadores en Python?

188

Quiero cambiar el siguiente código

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

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

a este código:

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

Me sale el error:

tipos de operandos no admitidos para +: 'generador' y 'generador'

¿Cómo unir dos generadores en Python?

Homer Xing
fuente
1
También me gustaría que Python funcione de esta manera. ¡Tengo exactamente el mismo error!
Adam Kurkiewicz

Respuestas:

236

Creo que itertools.chain()debería hacerlo.

Philipp
fuente
55
Hay que tener en cuenta que el valor de retorno de itertools.chain()no devuelve una types.GeneratorTypeinstancia. Por si acaso el tipo exacto es crucial.
Riga
1
¿Por qué no escribes también un ejemplo resuelto?
Charlie Parker
75

Un ejemplo de código:

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
fuente
10
¿Por qué no agregar este ejemplo a la itertools.chain()respuesta ya altamente votada ?
Jean-François Corbett
51

En Python (3.5 o superior) puedes hacer:

def concat(a, b):
    yield from a
    yield from b
Uduse
fuente
77
Tanto pitónico.
Ramazan Polat
9
Más general: def chain(*iterables): for iterable in iterables: yield from iterable(Ponga el defy foren líneas separadas cuando lo ejecute.)
wjandrea
¿Se produce todo desde a antes de que se produzca algo desde b o se están alternando?
problemofficer
@problemofficer Sí. Solo ase verifica hasta que todo se obtiene de él, incluso si bno es un iterador. El TypeErrorpor bno ser un iterador aparecerá más tarde.
Gee transit
36

Ejemplo simple:

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
usuario1767754
fuente
3
¿Por qué no agregar este ejemplo a la itertools.chain()respuesta ya altamente votada ?
Jean-François Corbett
Esto no es del todo correcto, ya que itertools.chaindevuelve un iterador, no un generador.
David J.
¿No puedes simplemente hacer chain([1, 2, 3], [3, 4, 5])?
Corman
10

Con itertools.chain.from_iterable puedes hacer cosas como:

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)
Andrew Paté
fuente
Estás utilizando una lista de comprensión innecesaria. También está utilizando una expresión generadora innecesaria gennycuando ya devuelve un generador. list(itertools.chain.from_iterable(genny(x)))Es mucho más conciso.
Corman
La comprensión! Ist fue una manera fácil de crear los dos generadores, según la pregunta. Tal vez mi respuesta es un poco complicada a ese respecto.
Andrew Paté
Supongo que la razón por la que agregué esta respuesta a las existentes fue para ayudar a aquellos que tienen muchos generadores con los que lidiar.
Andrew Paté
No es una manera fácil, hay muchas formas más fáciles. El uso de expresiones de generador en un generador existente reducirá el rendimiento, y el listconstructor es mucho más legible que la comprensión de la lista. Su método es mucho más ilegible en ese sentido.
Corman
Corman, estoy de acuerdo en que tu constructor de listas es más legible. Sin embargo, sería bueno ver tus 'formas más fáciles' ... Creo que el comentario anterior de wjandrea parece hacer lo mismo que itertools.chain.from_iterable sería bueno competir con ellos y ver quién es más rápido.
Andrew Paté
8

Aquí está usando una expresión generadora con fors anidado :

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
fuente
2
Una pequeña explicación no dolería.
Ramazan Polat
Bueno, no creo que pueda explicar esto mejor que la documentación de Python.
Alexey
(La documentación para las expresiones generadoras está vinculada desde mi respuesta. No veo una buena razón para copiar y pegar la documentación en mi respuesta.)
Alexey
3

También se puede usar el operador de desempaquetado *:

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

NOTA: Funciona de manera más eficiente para iterables 'no perezosos'. También se puede usar con diferentes tipos de comprensión. La forma preferida para el generador de concat sería de la respuesta de @Uduse

sol25
fuente
1

Si desea mantener los generadores separados pero aún iterar sobre ellos al mismo tiempo, puede usar zip ():

NOTA: La iteración se detiene en el más corto de los dos generadores.

Por ejemplo:

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
Dividir entre cero
fuente
0

Digamos que tenemos que generar (gen1 y gen 2) y queremos realizar algún cálculo adicional que requiera el resultado de ambos. Podemos devolver el resultado de dicha función / cálculo a través del método de mapa, que a su vez devuelve un generador que podemos recorrer.

En este escenario, la función / cálculo debe implementarse mediante la función lambda. La parte difícil es lo que pretendemos hacer dentro del mapa y su función lambda.

Forma general de solución propuesta:

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

Todas esas soluciones complicadas ...

solo haz:

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

Si realmente quiere "unir" ambos generadores, haga lo siguiente:

for directory, dirs, files in 
        [x for osw in [os.walk(director_1), os.walk(director_2)] 
               for x in osw]:
    do_something()
Camión
fuente
0

Yo diría que, como se sugiere en los comentarios del usuario "wjandrea", la mejor solución es

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

No cambia el tipo devuelto y es realmente pitónico.

Luca Di Liello
fuente