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?


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

Jawaban:


236

Saya pikir itertools.chain()harus melakukannya.


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

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

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

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)

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]

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


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

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

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()

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.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.