Saya mengalami kesulitan membungkus otak saya di sekitar PEP 380 .
- Apa situasi di mana "hasil dari" berguna?
- Apa kasus penggunaan klasik?
- Mengapa dibandingkan dengan micro-threads?
[perbarui]
Sekarang saya mengerti penyebab kesulitan saya. Saya telah menggunakan generator, tetapi tidak pernah benar-benar menggunakan coroutine (diperkenalkan oleh PEP-342 ). Meskipun ada beberapa kesamaan, generator dan coroutine pada dasarnya adalah dua konsep yang berbeda. Memahami coroutine (bukan hanya generator) adalah kunci untuk memahami sintaks baru.
Coroutine IMHO adalah fitur Python yang paling tidak jelas , sebagian besar buku membuatnya tampak tidak berguna dan tidak menarik.
Terima kasih atas jawaban yang luar biasa, tetapi terima kasih khusus kepada agf dan komentarnya yang menghubungkan dengan presentasi David Beazley . David batu.
Jawaban:
Mari kita menyingkir dulu. Penjelasan yang
yield from g
setara denganfor v in g: yield v
bahkan tidak mulai melakukan keadilan untuk apayield from
semua tentang. Karena, mari kita hadapi itu, jika semuayield from
memang memperluasfor
loop, maka itu tidak menjamin menambahyield from
bahasa dan menghalangi sejumlah fitur baru diimplementasikan dalam Python 2.x.Apa yang
yield from
dilakukan adalah membuat koneksi dua arah transparan antara pemanggil dan sub-generator :Koneksi "transparan" dalam arti bahwa itu akan menyebarkan semuanya dengan benar juga, bukan hanya elemen yang dihasilkan (mis. Pengecualian diperbanyak).
Koneksi adalah "dua arah" dalam arti bahwa data dapat dikirim dari dan ke generator.
( Jika kami berbicara tentang TCP,
yield from g
mungkin berarti "sekarang lepaskan sementara soket klien saya dan sambungkan kembali ke soket server lain ini". )BTW, jika Anda tidak yakin apa artinya mengirim data ke generator , Anda harus meninggalkan semuanya dan membaca tentang coroutine terlebih dahulu — mereka sangat berguna (kontraskan dengan subrutin ), tetapi sayangnya kurang dikenal dengan Python. Kursus Curious karya Dave Beazley tentang Coroutines adalah awal yang baik. Baca slide 24-33 untuk primer cepat.
Membaca data dari generator menggunakan hasil dari
Alih-alih secara manual mengulangi
reader()
, kita bisayield from
melakukannya.Itu berhasil, dan kami menghilangkan satu baris kode. Dan mungkin maksudnya sedikit lebih jelas (atau tidak). Tapi tidak ada kehidupan yang berubah.
Mengirim data ke generator (coroutine) menggunakan hasil dari - Bagian 1
Sekarang mari kita lakukan sesuatu yang lebih menarik. Mari kita buat coroutine yang disebut
writer
yang menerima data yang dikirim kepadanya dan menulis ke soket, fd, dll.Sekarang pertanyaannya adalah, bagaimana seharusnya fungsi pembungkus menangani pengiriman data ke penulis, sehingga setiap data yang dikirim ke pembungkus dikirim secara transparan ke
writer()
?Pembungkus perlu menerima data yang dikirim ke sana (jelas) dan juga harus menangani
StopIteration
ketika loop for habis. Jelas hanya melakukanfor x in coro: yield x
tidak akan berhasil. Ini adalah versi yang berfungsi.Atau, kita bisa melakukan ini.
Itu menghemat 6 baris kode, membuatnya jauh lebih mudah dibaca dan hanya berfungsi. Sihir!
Mengirim data ke generator hasil dari - Bagian 2 - Penanganan pengecualian
Mari kita buat lebih rumit. Bagaimana jika penulis kita perlu menangani pengecualian? Katakanlah
writer
gagang aSpamException
dan mencetak***
jika bertemu satu.Bagaimana jika kita tidak berubah
writer_wrapper
? Apakah itu bekerja? Mari mencobaUm, itu tidak berfungsi karena
x = (yield)
hanya menimbulkan pengecualian dan semuanya terhenti. Mari kita membuatnya bekerja, tetapi secara manual menangani pengecualian dan mengirimkannya atau melemparkannya ke sub-generator (writer
)Ini bekerja.
Tapi begitu juga ini!
Secara
yield from
transparan menangani pengiriman nilai-nilai atau melemparkan nilai-nilai ke dalam sub-generator.Ini masih belum mencakup semua kasus sudut. Apa yang terjadi jika generator luar ditutup? Bagaimana dengan kasus ketika sub-generator mengembalikan nilai (ya, dengan Python 3.3+, generator dapat mengembalikan nilai), bagaimana seharusnya nilai pengembalian disebarkan? Yang
yield from
transparan menangani semua kasus sudut sangat mengesankan .yield from
hanya bekerja secara ajaib dan menangani semua kasus itu.Saya pribadi merasa
yield from
adalah pilihan kata kunci yang buruk karena tidak membuat sifat dua arah menjadi jelas. Ada kata kunci lain yang diusulkan (sepertidelegate
tetapi ditolak karena menambahkan kata kunci baru ke bahasa ini jauh lebih sulit daripada menggabungkan yang sudah ada.Singkatnya, yang terbaik
yield from
adalahtransparent two way channel
antara penelepon dan sub-generator.Referensi:
sumber
except StopIteration: pass
DI DALAMwhile True:
loop bukan representasi akuratyield from coro
- yang bukan loop tak terbatas dan setelahcoro
habis (yaitu menimbulkan StopIteration),writer_wrapper
akan menjalankan pernyataan berikutnya. Setelah pernyataan terakhir, ia akan otomatis dinaikkanStopIteration
karena generator yang kelelahan ...writer
terkandungfor _ in range(4)
alih-alihwhile True
, maka setelah mencetaknya>> 3
JUGA akan meningkatkan otomatisStopIteration
dan ini akan ditangani secara otomatis olehyield from
dan kemudianwriter_wrapper
akan otomatis meningkatkan itu sendiriStopIteration
dan karenawrap.send(i)
tidak di dalamtry
blok, itu akan benar-benar dinaikkan pada titik ini ( yaitu traceback hanya akan melaporkan saluran denganwrap.send(i)
, bukan apa pun dari dalam generator)Setiap situasi di mana Anda memiliki lingkaran seperti ini:
Seperti yang dijelaskan oleh PEP, ini adalah upaya yang agak naif dalam menggunakan subgenerator, ini kehilangan beberapa aspek, terutama penanganan mekanisme
.throw()
/.send()
/.close()
yang diperkenalkan oleh PEP 342 . Untuk melakukan ini dengan benar, kode yang agak rumit diperlukan.Pertimbangkan bahwa Anda ingin mengekstrak informasi dari struktur data rekursif. Katakanlah kita ingin mendapatkan semua simpul daun di pohon:
Yang lebih penting adalah kenyataan bahwa sampai saat itu
yield from
, tidak ada metode sederhana untuk refactoring kode generator. Misalkan Anda memiliki generator (tidak masuk akal) seperti ini:Sekarang Anda memutuskan untuk memfaktorkan loop ini ke generator terpisah. Tanpa
yield from
, ini jelek, sampai pada titik di mana Anda akan berpikir dua kali apakah Anda benar-benar ingin melakukannya. Denganyield from
, sebenarnya menyenangkan untuk melihat:Saya pikir apa yang dibicarakan oleh bagian dalam PEP ini adalah bahwa setiap generator memiliki konteks eksekusi tersendiri. Bersama dengan fakta bahwa eksekusi diaktifkan antara generator-iterator dan pemanggil menggunakan
yield
dan__next__()
, masing-masing, ini mirip dengan utas, di mana sistem operasi mengalihkan utas pelaksana dari waktu ke waktu, bersama dengan konteks eksekusi (stack, register, ...).Efek dari ini juga sebanding: Baik generator-iterator dan pemanggil mengalami kemajuan dalam kondisi eksekusi mereka pada saat yang sama, eksekusi mereka saling terkait. Misalnya, jika generator melakukan semacam perhitungan dan penelepon mencetak hasilnya, Anda akan melihat hasilnya segera setelah tersedia. Ini adalah bentuk konkurensi.
Analogi itu bukan sesuatu yang spesifik
yield from
, - ini adalah properti umum dari generator dengan Python.sumber
get_list_values_as_xxx
adalah generator sederhana dengan satu barisfor x in input_param: yield int(x)
dan dua lainnya masing-masing denganstr
danfloat
Di mana pun Anda menjalankan generator dari dalam generator Anda memerlukan "memompa" untuk kembali
yield
nilai-nilai:for v in inner_generator: yield v
. Seperti yang ditunjukkan oleh PEP, ada kerumitan halus yang diabaikan oleh kebanyakan orang. Kontrol aliran non-lokal sepertithrow()
adalah salah satu contoh yang diberikan dalam PEP. Sintaks baruyield from inner_generator
digunakan di mana pun Anda akan menulisfor
loop eksplisit sebelumnya. Ini bukan hanya gula sintaksis, ia menangani semua kotak sudut yang diabaikan olehfor
loop. Menjadi "manis" mendorong orang untuk menggunakannya dan dengan demikian mendapatkan perilaku yang benar.Pesan ini dalam utas diskusi berbicara tentang kompleksitas ini:
Saya tidak dapat berbicara dengan perbandingan dengan micro-threads, selain untuk mengamati bahwa generator adalah jenis paralellism. Anda dapat menganggap generator yang ditangguhkan sebagai utas yang mengirimkan nilai melalui
yield
utas konsumen. Implementasi aktual mungkin tidak seperti ini (dan implementasi sebenarnya jelas sangat menarik bagi pengembang Python) tetapi ini tidak menjadi perhatian pengguna.yield from
Sintaks baru tidak menambahkan kemampuan tambahan ke bahasa dalam hal threading, itu hanya membuatnya lebih mudah untuk menggunakan fitur yang ada dengan benar. Atau lebih tepatnya hal itu membuatnya lebih mudah bagi konsumen pemula dari generator batin yang kompleks yang ditulis oleh seorang ahli untuk melewati generator itu tanpa merusak salah satu fitur kompleksnya.sumber
Contoh singkat akan membantu Anda memahami salah satu
yield from
use case: dapatkan nilai dari generator lainsumber
print(*flatten([1, [2], [3, [4]]]))
yield from
pada dasarnya rantai iterator dengan cara yang efisien:Seperti yang Anda lihat, ia menghapus satu loop Python murni. Itu cukup banyak, tetapi rantai iterator adalah pola yang cukup umum di Python.
Utas pada dasarnya adalah fitur yang memungkinkan Anda untuk melompat keluar dari fungsi pada titik yang benar-benar acak dan melompat kembali ke keadaan fungsi lainnya. Pengawas utas melakukan ini sangat sering, sehingga program tampaknya menjalankan semua fungsi ini secara bersamaan. Masalahnya adalah bahwa poin-poin itu acak, sehingga Anda perlu menggunakan penguncian untuk mencegah pengawas menghentikan fungsi pada titik yang bermasalah.
Generator sangat mirip dengan utas dalam hal ini: Generator memungkinkan Anda menentukan titik tertentu (kapan pun mereka
yield
) di mana Anda dapat melompat masuk dan keluar. Ketika digunakan dengan cara ini, generator disebut coroutine.Baca tutorial luar biasa ini tentang coroutine dengan Python untuk lebih jelasnya
sumber
throw()/send()/close()
merupakanyield
fitur yangyield from
jelas harus diimplementasikan dengan benar karena seharusnya menyederhanakan kode. Hal-hal sepele seperti itu tidak ada hubungannya dengan penggunaan.chain
fungsi Anda sendiri karenaitertools.chain
sudah ada. Gunakanyield from itertools.chain(*iters)
.Dalam penggunaan yang diterapkan untuk Asynchronous IO coroutine ,
yield from
memiliki perilaku yang sama sepertiawait
pada fungsi coroutine . Keduanya digunakan untuk menunda eksekusi coroutine.yield from
digunakan oleh coroutine berbasis generator .await
digunakan untukasync def
coroutine. (sejak Python 3.5+)Untuk Asyncio, jika tidak perlu mendukung versi Python yang lebih lama (yaitu> 3.5),
async def
/await
adalah sintaks yang disarankan untuk mendefinisikan coroutine. Dengan demikianyield from
tidak diperlukan lagi dalam coroutine.Tetapi secara umum di luar asyncio,
yield from <sub-generator>
masih ada beberapa penggunaan lain dalam iterasi sub-generator seperti yang disebutkan dalam jawaban sebelumnya.sumber
Kode ini mendefinisikan fungsi
fixed_sum_digits
mengembalikan generator yang menghitung semua angka enam digit sehingga jumlah digit adalah 20.Cobalah untuk menulisnya tanpa
yield from
. Jika Anda menemukan cara yang efektif untuk melakukannya, beri tahu saya.Saya pikir untuk kasus seperti ini: mengunjungi pohon,
yield from
membuat kodenya lebih mudah dan bersih.sumber
Sederhananya,
yield from
memberikan rekursi ekor untuk fungsi iterator.sumber