Como juntar dois geradores em Python?

188

Eu quero mudar o seguinte código

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

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

para este código:

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

Eu recebo o erro:

tipo (s) de operando não suportado para +: 'generator' e 'generator'

Como juntar dois geradores em Python?

Homer Xing
fonte
1
Eu também gostaria que o Python funcionasse dessa maneira. Tem exatamente o mesmo erro!
Adam Kurkiewicz 28/08

Respostas:

236

Eu acho que itertools.chain()deveria fazê-lo.

Philipp
fonte
5
Deve-se ter em mente que o valor de retorno de itertools.chain()não retorna uma types.GeneratorTypeinstância. Apenas no caso de o tipo exato ser crucial.
Riga
1
por que você também não escreve um exemplo elaborado?
Charlie Parker
75

Um exemplo 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
fonte
10
Por que não adicionar este exemplo à itertools.chain()resposta já existente e altamente votada ?
Jean-François Corbett
51

No Python (3.5 ou superior), você pode fazer:

def concat(a, b):
    yield from a
    yield from b
Uduse
fonte
7
Tão pitônico.
Ramazan Polat 28/10
9
Mais geral: def chain(*iterables): for iterable in iterables: yield from iterable(Coloque o defe for. Em linhas separadas quando você executá-lo)
wjandrea
Tudo a partir de a é produzido antes que qualquer coisa de b seja produzida ou eles estão sendo alternados?
problemofficer
@problemofficer Yup. Somente aé verificado até que tudo seja gerado, mesmo que bnão seja um iterador. O TypeErrorpor bnão ser um iterador será exibido mais tarde.
GeeTransit 04/01
36

Exemplo simples:

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
fonte
3
Por que não adicionar este exemplo à itertools.chain()resposta já existente e altamente votada ?
Jean-François Corbett
Isso não está certo, pois itertools.chainretorna um iterador, não um gerador.
David J.
Você não pode simplesmente fazer chain([1, 2, 3], [3, 4, 5])?
Corman
10

Com itertools.chain.from_iterable, você pode fazer coisas 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 pate
fonte
Você está usando uma compreensão desnecessária da lista. Você também está usando uma expressão desnecessária de gerador gennyquando ele já retorna um gerador. list(itertools.chain.from_iterable(genny(x)))é muito mais conciso.
Corman
A compreensão! Ist foi uma maneira fácil de criar os dois geradores, conforme a pergunta. Talvez minha resposta seja um pouco complicada a esse respeito.
andrew pate
Acho que a razão pela qual adicionei essa resposta às existentes foi ajudar aqueles que têm muitos geradores para lidar.
andrew pate
Não é uma maneira fácil, existem muitas maneiras mais fáceis. Usar expressões de gerador em um gerador existente reduzirá o desempenho e o listconstrutor será muito mais legível que a compreensão da lista. Seu método é muito mais ilegível a esse respeito.
Corman
Corman, concordo que o construtor da sua lista é realmente mais legível. Seria bom ver suas 'maneiras mais fáceis' embora ... Acho que o comentário de wjandrea acima parece fazer o mesmo que itertools.chain.from_iterable seria bom correr com eles e ver quem é mais rápido.
andrew pate
8

Aqui está usando uma expressão de gerador com fors aninhados :

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
fonte
2
Uma pequena explicação não faria mal.
Ramazan Polat 28/10
Bem, acho que não posso explicar isso melhor do que a documentação do Python.
Alexey #
(A documentação para gerador de expressões está ligada a partir minha resposta eu não vejo uma boa razão para copiar e colar a documentação para a minha resposta..)
Alexey
3

Pode-se também usar o operador de desempacotamento *:

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

NOTA: Funciona com mais eficiência para iteráveis ​​'não preguiçosos'. Também pode ser usado com diferentes tipos de compreensão. A maneira preferida para concat do gerador seria a partir da resposta de @Uduse

sol25
fonte
1

Se você deseja manter os geradores separados, mas ainda iterar sobre eles ao mesmo tempo, você pode usar zip ():

NOTA: A iteração para no menor dos dois geradores

Por exemplo:

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
Divida por zero
fonte
0

Digamos que precisamos de geradores (geração 1 e geração 2) e queremos realizar algum cálculo extra que exija o resultado de ambos. Podemos retornar o resultado dessa função / cálculo por meio do método map, que por sua vez retorna um gerador no qual podemos fazer um loop.

Nesse cenário, a função / cálculo precisa ser implementada através da função lambda. A parte complicada é o que pretendemos fazer dentro do mapa e sua função lambda.

Forma geral da solução proposta:

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

Todas essas soluções complicadas ...

apenas faça:

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

Se você realmente deseja "unir" os dois geradores, faça:

for directory, dirs, files in 
        [x for osw in [os.walk(director_1), os.walk(director_2)] 
               for x in osw]:
    do_something()
Camion
fonte
0

Eu diria que, como sugerido nos comentários do usuário "wjandrea", a melhor solução é

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

Não altera o tipo retornado e é realmente python.

Luca Di Liello
fonte