¿Cómo unir dos generadores (u otros iterables) en Python?

7 minutos de lectura

Avatar de usuario de Homer Xing
Homero Xing

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?

Avatar de usuario de Philipp
Felipe

itertools.chain() Deberías hacerlo. Toma múltiples iterables y rendimientos de cada uno, aproximadamente equivalente a:

def chain(*iterables):
    for it in iterables:
        for element in it:
            yield element

Ejemplo de uso:

from itertools import chain

g = (c for c in 'ABC')  # Dummy generator, just for example
c = chain(g, 'DEF')  # Chain the generator and a string
for item in c:
    print(item)

Producción:

A
B
C
D
E
F

  • Se debe tener en cuenta que el valor de retorno de itertools.chain() no devuelve un types.GeneratorType instancia. En caso de que el tipo exacto sea crucial.

    – Riga

    16 sep 2019 a las 13:45


  • Ver @andrew-pate anser para itertools.chain.from_iterable() referencia para devolver una instancia de types.GeneratorType.

    – gkedge

    9 de septiembre de 2020 a las 13:13


  • itertools.chain() daría todos los elementos en un directorio y luego cambiaría al otro directorio. Ahora, ¿cómo elegimos los primeros elementos de ambos directorios y realizamos algunas operaciones, y luego cambiamos al siguiente par y así sucesivamente? Cualquier idea sera apreciada.

    – yash

    9 de febrero de 2021 a las 11:07

  • @yash Iterar sobre esos directorios manualmente usando la función incorporada próximo.

    – Jeyekomon

    16 de febrero de 2021 a las 8:57

  • @yash te puede gustar Código Postal. Hace precisamente eso, selecciona los valores primero, segundo, etc. y los pone en tuplas.

    – Randelung

    18 mayo 2021 a las 21:00


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

  • ¿Por qué no agregar este ejemplo a los ya existentes, altamente votados? itertools.chain() ¿respuesta?

    – Jean-François Corbett

    15 de octubre de 2018 a las 7:13

  • Um. Porque le hubiera costado 850 rep. El tipo tiene 851. Tú sí, cesio.

    – Tatarizar

    14 de diciembre de 2020 a las 10:38

  • @ Jean-FrançoisCorbett, la persona que escribió la respuesta “ya existente” podría haber hecho eso realmente … ¿de acuerdo? 🙂

    – Oso de hielo

    27 de diciembre de 2020 a las 10:37

  • Se ha agregado un ejemplo a la respuesta principal, lo que hace que esto sea redundante.

    – wjandrea

    17 de julio de 2022 a las 0:52

Avatar de usuario de Uduse
uduse

En Python (3.5 o superior) puedes hacer:

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

  • Mucho pitónico.

    – ramazán polat

    28 oct 2018 a las 18:18

  • Mas general: def chain(*iterables): for iterable in iterables: yield from iterable (Pon el def y for en líneas separadas cuando lo ejecuta).

    – wjandrea

    12 de abril de 2019 a las 15:29

  • es todo de a cedido ante cualquier cosa de b se ceden o se alternan?

    – oficial de problemas – nf Monica

    10 de diciembre de 2019 a las 16:22


  • @problemofficer Sí. Solo a se verifica hasta que se obtiene todo de él, incluso si b no es un iterador. Él TypeError por b no ser un iterador aparecerá más adelante.

    – GeeTransit

    4 de enero de 2020 a las 16:10

  • @Karolius Oh, está bien, veo lo que estás diciendo. Parece que cometiste un error tipográfico, lo que me confundió: def chain(iterable) debiera ser def chain(iterables). (También, x for x in es redundante). De todos modos, ya hay una herramienta en stdlib que hace eso: itertools.chain.from_iterable. Y más allá del rendimiento, si tuvieras un infinito iterable de iterables, es no sería posible para usar el desembalaje.

    – wjandrea

    17 de julio de 2022 a las 1:22


avatar de usuario de user1767754
usuario1767754

Ejemplo sencillo:

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

avatar de usuario de andrew pate
paté andrés

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á utilizando una comprensión de lista innecesaria. También está utilizando una expresión de generador innecesaria en genny cuando ya devuelve un generador. list(itertools.chain.from_iterable(genny(x))) es mucho más conciso.

    – Cormán

    25 mayo 2020 a las 20:31


  • La comprensión de la lista fue una manera fácil de crear los dos generadores, según la pregunta. Tal vez mi respuesta es un poco complicada en ese sentido.

    – andrew paté

    26 mayo 2020 a las 22:29

  • 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é

    26 mayo 2020 a las 22:41


  • No es una manera fácil, hay muchas maneras más fáciles. El uso de expresiones de generador en un generador existente reducirá el rendimiento y el list constructor es mucho más legible que la lista de comprensión. Su método es mucho más ilegible en esos aspectos.

    – Cormán

    27 de mayo de 2020 a las 0:48

  • Corman, estoy de acuerdo en que su constructor de listas es más legible. Sin embargo, sería bueno ver sus ‘muchas 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 el más rápido.

    – andrew paté

    27 de mayo de 2020 a las 9:25

Avatar de usuario de Robert Siemer
Roberto Siemer

Aquí está usando un generador de expresión con anidado fors:

range_a = range(3)
range_b = range(5)
result = (item
    for one_range in (range_a, range_b)
    for item in one_range)
assert list(result) == [0, 1, 2, 0, 1, 2, 3, 4]

Él for ... in ... se evalúan de izquierda a derecha. El identificador después for establece una nueva variable. Tiempo one_range en uso en el siguiente for ... in ...la item del segundo se usa en la expresión de asignación „final” de la cual solo hay una (al principio).

  • Está utilizando una comprensión de lista innecesaria. También está utilizando una expresión de generador innecesaria en genny cuando ya devuelve un generador. list(itertools.chain.from_iterable(genny(x))) es mucho más conciso.

    – Cormán

    25 mayo 2020 a las 20:31


  • La comprensión de la lista fue una manera fácil de crear los dos generadores, según la pregunta. Tal vez mi respuesta es un poco complicada en ese sentido.

    – andrew paté

    26 mayo 2020 a las 22:29

  • 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é

    26 mayo 2020 a las 22:41


  • No es una manera fácil, hay muchas maneras más fáciles. El uso de expresiones de generador en un generador existente reducirá el rendimiento y el list constructor es mucho más legible que la lista de comprensión. Su método es mucho más ilegible en esos aspectos.

    – Cormán

    27 de mayo de 2020 a las 0:48

  • Corman, estoy de acuerdo en que su constructor de listas es más legible. Sin embargo, sería bueno ver sus ‘muchas 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 el más rápido.

    – andrew paté

    27 de mayo de 2020 a las 9:25

avatar de usuario de wjandrea
andrea

Actualización de 2020: funciona tanto en Python 3 como en Python 2

import itertools

iterA = range(10,15)
iterB = range(15,20)
iterC = range(20,25)

primera opción

for i in itertools.chain(iterA, iterB, iterC):
    print(i)

# 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

opción alternativa, introducida en python 2.6

for i in itertools.chain.from_iterable( [iterA, iterB, iterC] ):
    print(i)

# 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

itertools.cadena() es el básico.

itertools.chain.from_iterable() es útil si tiene un iterable de iterables. Por ejemplo, una lista de archivos por subdirectorio como [ ["src/server.py", "src/readme.txt"], ["test/test.py"] ].

  • Python 2 finalizó el 1 de enero de 2020, así que me sorprende que lo menciones

    – wjandrea

    17 de julio de 2022 a las 1:31

¿Ha sido útil esta solución?