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?

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

Réponses:

242

Je pense que je itertools.chain()devrais le faire.

Philipp
la source
7
Il faut garder à l'esprit que la valeur de retour de itertools.chain()ne renvoie pas d' types.GeneratorTypeinstance. Juste au cas où le type exact serait crucial.
Riga le
1
pourquoi n'écrivez-vous pas également un exemple élaboré?
Charlie Parker le
Voir @ andrew-pate anser pour la référence itertools.chain.from_iterable () pour renvoyer une instance de types.GeneratorType.
gkedge le
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
Cesio
la source
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
Uduse
la source
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
user1767754
la source
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)
andrew pate
la source
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]
Alexey
la source
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

sol25
la source
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
Diviser par zéro
la source
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
Mahdi Ghelichi
la source
0

Je dirais que, comme suggéré dans les commentaires de l'utilisateur "wjandrea", la meilleure solution est

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

Il ne change pas le type retourné et est vraiment pythonique.

Luca Di Liello
la source
C'est ce que itertools.chain.from_iterable () fera pour vous. Voir la réponse de @ andrew-pate .
gkedge le
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"] ].

user5994461
la source
-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()
Camion
la source
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