¿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?


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.


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

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

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

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)

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]

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


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

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

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

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.