Bagaimana cara bergabung dengan dua generator di Python?

188

Saya ingin mengubah kode berikut

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

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

ke kode ini:

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

Saya mendapatkan kesalahan:

jenis operan yang tidak didukung untuk +: 'generator' dan 'generator'

Bagaimana cara bergabung dengan dua generator di Python?

Homer Xing
sumber
1
Saya juga ingin Python bekerja dengan cara ini. Mendapat kesalahan yang persis sama!
Adam Kurkiewicz

Jawaban:

236

Saya pikir itertools.chain()harus melakukannya.

Philipp
sumber
5
Orang harus ingat bahwa nilai balik itertools.chain()tidak mengembalikan sebuah types.GeneratorTypeinstance. Untuk berjaga-jaga, tipe yang tepat sangat penting.
Riga
1
kenapa tidak Anda juga menuliskan contoh yang berhasil?
Charlie Parker
75

Contoh kode:

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
sumber
10
Mengapa tidak menambahkan contoh ini ke jawaban yang sudah ada dan sangat tervvatifikasi itertools.chain()?
Jean-François Corbett
51

Dengan Python (3,5 atau lebih besar) yang dapat Anda lakukan:

def concat(a, b):
    yield from a
    yield from b
Uduse
sumber
7
Begitu banyak pythonic.
Ramazan Polat
9
Lebih umum: def chain(*iterables): for iterable in iterables: yield from iterable(Tempatkan defdan forpada baris terpisah ketika Anda menjalankannya.)
wjandrea
Apakah segala sesuatu dari yang dihasilkan sebelum apa pun dari b dihasilkan atau apakah mereka sedang berganti?
problemofficer
@problemofficer Yup. Hanya adiperiksa sampai semuanya dihasilkan darinya, meskipun bbukan iterator. The TypeErroruntuk btidak menjadi iterator akan datang kemudian.
GeeTransit
36

Contoh sederhana:

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
pengguna1767754
sumber
3
Mengapa tidak menambahkan contoh ini ke jawaban yang sudah ada dan sangat tervvatifikasi itertools.chain()?
Jean-François Corbett
Ini tidak benar, karena itertools.chainmengembalikan iterator, bukan generator.
David J.
Tidak bisakah kamu melakukannya chain([1, 2, 3], [3, 4, 5])?
Corman
10

Dengan itertools.chain.from_iterable Anda dapat melakukan hal-hal seperti:

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
sumber
Anda menggunakan pemahaman daftar yang tidak perlu. Anda juga menggunakan ekspresi generator yang tidak perlu gennyketika sudah mengembalikan generator. list(itertools.chain.from_iterable(genny(x)))jauh lebih ringkas.
Corman
Pemahaman! Itu adalah cara mudah untuk membuat dua generator, sesuai pertanyaan. Mungkin jawaban saya agak berbelit-belit dalam hal itu.
andrew pate
Saya kira alasan saya menambahkan jawaban ini ke yang sudah ada adalah untuk membantu mereka yang kebetulan memiliki banyak generator untuk menangani.
andrew pate
Itu bukan cara yang mudah, ada banyak cara yang lebih mudah. Menggunakan ekspresi generator pada generator yang ada akan menurunkan kinerja, dan listkonstruktor jauh lebih mudah dibaca daripada pemahaman daftar. Metode Anda jauh lebih tidak dapat dibaca dalam hal itu.
Corman
Corman, saya setuju konstruktor daftar Anda memang lebih mudah dibaca. Akan lebih baik untuk melihat 'banyak cara mudah' Anda ... Saya pikir komentar wjandrea di atas terlihat melakukan hal yang sama seperti itertools.chain.from_iterable akan lebih baik untuk membalap mereka dan melihat siapa yang tercepat.
andrew pate
8

Ini dia menggunakan ekspresi generator dengan nested fors:

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
sumber
2
Sedikit penjelasan tidak akan sakit.
Ramazan Polat
Yah, saya tidak berpikir saya bisa menjelaskan ini lebih baik daripada dokumentasi Python.
Alexey
(Dokumentasi untuk ekspresi generator ditautkan dari jawaban saya. Saya tidak melihat alasan yang bagus untuk menyalin dan menempelkan dokumentasi ke jawaban saya.)
Alexey
3

Satu juga dapat menggunakan operator membongkar *:

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

CATATAN: Bekerja paling efisien untuk iterables 'non-malas'. Dapat juga digunakan dengan berbagai jenis pemahaman. Cara yang disukai untuk concat generator adalah dari jawaban dari @Uduse

sol25
sumber
1

Jika Anda ingin membuat generator terpisah tetapi tetap iterate pada mereka pada saat yang sama Anda dapat menggunakan zip ():

CATATAN: Iterasi berhenti di yang lebih pendek dari dua generator

Sebagai contoh:

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
Dibagi nol
sumber
0

Katakanlah kita harus generator (gen1 dan gen 2) dan kami ingin melakukan beberapa perhitungan tambahan yang membutuhkan hasil dari keduanya. Kita dapat mengembalikan hasil fungsi / perhitungan tersebut melalui metode peta, yang pada gilirannya mengembalikan generator yang bisa kita lewati.

Dalam skenario ini, fungsi / perhitungan perlu diimplementasikan melalui fungsi lambda. Bagian yang sulit adalah apa yang ingin kita lakukan di dalam peta dan fungsi lambda-nya.

Bentuk umum dari solusi yang diusulkan:

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

Semua solusi rumit itu ...

kerjakan saja:

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

Jika Anda benar-benar ingin "bergabung" dengan kedua generator, maka lakukan:

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

Saya akan mengatakan itu, seperti yang disarankan dalam komentar oleh pengguna "wjandrea", solusi terbaik adalah

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

Itu tidak mengubah tipe yang dikembalikan dan benar-benar pythonic.

Luca Di Liello
sumber