Comment joindre deux générateurs en Python?


192

Je souhaite modifier le code suivant

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

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

à ce code:

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

J'obtiens l'erreur:

type (s) d'opérande non pris en charge pour +: 'générateur' et 'générateur'

Comment joindre deux générateurs en Python?


1
J'aimerais aussi que Python fonctionne de cette façon. Vous avez exactement la même erreur!
Adam Kurkiewicz

Réponses:



82

Un exemple de code:

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

17
Pourquoi ne pas ajouter cet exemple à la itertools.chain()réponse déjà existante et fortement votée ?
Jean-François Corbett

55

En Python (3.5 ou supérieur), vous pouvez faire:

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

7
Tellement pythonique.
Ramazan Polat

11
Plus général: def chain(*iterables): for iterable in iterables: yield from iterable(Mettez le defet forsur des lignes séparées lorsque vous l'exécutez.)
wjandrea

Tout ce qui provient de a est-il cédé avant que quoi que ce soit de b soit produit ou sont-ils alternés?
problemofficer

@problemofficer Yup. Seul aest vérifié jusqu'à ce que tout en soit tiré, même s'il bne s'agit pas d'un itérateur. Le fait TypeErrorde bne pas être un itérateur apparaîtra plus tard.
GeeTransit

38

Exemple 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

4
Pourquoi ne pas ajouter cet exemple à la itertools.chain()réponse déjà existante et fortement votée ?
Jean-François Corbett

Ce n'est pas tout à fait correct, car itertools.chainrenvoie un itérateur, pas un générateur.
David J.

Tu ne peux pas le faire chain([1, 2, 3], [3, 4, 5])?
Corman le

10

Avec itertools.chain.from_iterable, vous pouvez faire des choses comme:

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)

Vous utilisez une compréhension de liste inutile. Vous utilisez également une expression de générateur inutile gennylorsqu'elle renvoie déjà un générateur. list(itertools.chain.from_iterable(genny(x)))est beaucoup plus concis.
Corman

La compréhension! Ist était un moyen facile de créer les deux générateurs, conformément à la question. Ma réponse est peut-être un peu compliquée à cet égard.
andrew pate

J'imagine que la raison pour laquelle j'ai ajouté cette réponse à celles existantes était d'aider ceux qui ont beaucoup de générateurs à gérer.
andrew pate

Ce n'est pas un moyen facile, il existe de nombreux moyens plus simples. L'utilisation d'expressions de générateur sur un générateur existant diminuera les performances et le listconstructeur est beaucoup plus lisible que la compréhension de la liste. Votre méthode est beaucoup plus illisible à cet égard.
Corman

Corman, je suis d'accord que votre constructeur de liste est en effet plus lisible. Ce serait bien de voir vos `` nombreux moyens plus faciles '' cependant ... Je pense que le commentaire de wjandrea ci-dessus semble faire la même chose que itertools.chain.from_iterable, il serait bon de les faire courir et de voir qui est le plus rapide.
andrew pate

8

Ici, il utilise une expression de générateur avec des fors imbriqués :

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
Une petite explication ne ferait pas de mal.
Ramazan Polat

Eh bien, je ne pense pas pouvoir expliquer cela mieux que la documentation de Python.
Alexey

(La documentation pour les expressions génératrices est liée à ma réponse. Je ne vois pas de bonne raison de copier et coller la documentation dans ma réponse.)
Alexey

2

On peut également utiliser l'opérateur de décompression *:

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

REMARQUE: fonctionne plus efficacement pour les itérables «non paresseux». Peut également être utilisé avec différents types de compréhension. Le moyen préféré pour le générateur concat serait de la réponse de @Uduse


C'est triste qu'il n'y ait pas d'évaluation paresseuse du * générateur, car cela aurait fait de cette solution une merveilleuse ...
Camion

1
–1 cela consommera immédiatement les deux générateurs dans un tuple!
wim

0

Si vous voulez garder les générateurs séparés tout en continuant à les parcourir en même temps, vous pouvez utiliser zip ():

REMARQUE: l'itération s'arrête au plus court des deux générateurs

Par exemple:

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

Disons que nous devons générer des générateurs (gen1 et gen 2) et que nous voulons effectuer un calcul supplémentaire qui nécessite le résultat des deux. Nous pouvons renvoyer le résultat d'une telle fonction / calcul via la méthode map, qui à son tour renvoie un générateur sur lequel nous pouvons boucler.

Dans ce scénario, la fonction / le calcul doit être implémenté via la fonction lambda. La partie délicate est ce que nous cherchons à faire dans la carte et sa fonction lambda.

Forme générale de la solution proposée:

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


0

Mise à jour 2020: fonctionne à la fois en python 3 et python 2

import itertools

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

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
### alternative option, introduced in 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.chain () est la base.

itertools.chain.from_iterables est pratique si vous avez un itérable d'itérables. Par exemple, une liste de fichiers par sous-répertoire comme [ ["src/server.py", "src/readme.txt"], ["test/test.py"] ].


-2

Toutes ces solutions compliquées ...

fais juste:

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

Si vous voulez vraiment "rejoindre" les deux générateurs, alors faites:

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

Le deuxième extrait de code donne une erreur d'indentation. Cela peut être corrigé en entourant la compréhension de la liste avec des parenthèses: la parenthèse ouvrante doit être sur la même ligne inet la fermeture après la fin de la composition de la liste. Indépendamment de cette erreur, je pense que c'est un mauvais exemple à suivre. Il réduit la lisibilité en mélangeant l'indentation. Les itertools.chainréponses sont massivement plus lisibles et plus faciles à utiliser.
shynjax287

Vous n'avez pas besoin d'ajouter de parenthèses. Je viens de déplacer le crochet d'ouverture sur la ligne précédente pour résoudre ce problème. Au fait, vous n'aimez peut-être pas mon exemple, mais je pense toujours que c'est une bonne idée de savoir faire les choses par vous-même, car cela vous permet d'écrire vous-même la bibliothèque au lieu de recourir au travail de quelqu'un d'autre lorsque vous en avez besoin.
Camion

Bien sûr, c'est une bonne idée d'apprendre à faire les choses par vous-même. Je n'ai jamais débattu de ça. Désolé si je n'étais pas clair. L'utilisation d'une liste de compréhension réduit ici la lisibilité et n'est pas vraiment nécessaire. Les compréhensions de listes sont cool, les compréhensions de listes longues deviennent difficiles à lire et à corriger. Le code pourrait être amélioré en créant la liste avant, puis en l'itérant. Désolé pour mon commentaire entre parenthèses s'il était incorrect.
shynjax287
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.