Mari kita menyingkir dulu. Penjelasan yang yield from g
setara dengan for v in g: yield v
bahkan tidak mulai melakukan keadilan untuk apa yield from
semua tentang. Karena, mari kita hadapi itu, jika semua yield from
memang memperluas for
loop, maka itu tidak menjamin menambah yield 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
def reader():
"""A generator that fakes a read from a file, socket, etc."""
for i in range(4):
yield '<< %s' % i
def reader_wrapper(g):
# Manually iterate over data produced by reader
for v in g:
yield v
wrap = reader_wrapper(reader())
for i in wrap:
print(i)
# Result
<< 0
<< 1
<< 2
<< 3
Alih-alih secara manual mengulangi reader()
, kita bisa yield from
melakukannya.
def reader_wrapper(g):
yield from g
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.
def writer():
"""A coroutine that writes data *sent* to it to fd, socket, etc."""
while True:
w = (yield)
print('>> ', w)
Sekarang pertanyaannya adalah, bagaimana seharusnya fungsi pembungkus menangani pengiriman data ke penulis, sehingga setiap data yang dikirim ke pembungkus dikirim secara transparan ke writer()
?
def writer_wrapper(coro):
# TBD
pass
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in range(4):
wrap.send(i)
# Expected result
>> 0
>> 1
>> 2
>> 3
Pembungkus perlu menerima data yang dikirim ke sana (jelas) dan juga harus menangani StopIteration
ketika loop for habis. Jelas hanya melakukan for x in coro: yield x
tidak akan berhasil. Ini adalah versi yang berfungsi.
def writer_wrapper(coro):
coro.send(None) # prime the coro
while True:
try:
x = (yield) # Capture the value that's sent
coro.send(x) # and pass it to the writer
except StopIteration:
pass
Atau, kita bisa melakukan ini.
def writer_wrapper(coro):
yield from coro
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 a SpamException
dan mencetak ***
jika bertemu satu.
class SpamException(Exception):
pass
def writer():
while True:
try:
w = (yield)
except SpamException:
print('***')
else:
print('>> ', w)
Bagaimana jika kita tidak berubah writer_wrapper
? Apakah itu bekerja? Mari mencoba
# writer_wrapper same as above
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in [0, 1, 2, 'spam', 4]:
if i == 'spam':
wrap.throw(SpamException)
else:
wrap.send(i)
# Expected Result
>> 0
>> 1
>> 2
***
>> 4
# Actual Result
>> 0
>> 1
>> 2
Traceback (most recent call last):
... redacted ...
File ... in writer_wrapper
x = (yield)
__main__.SpamException
Um, 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
)
def writer_wrapper(coro):
"""Works. Manually catches exceptions and throws them"""
coro.send(None) # prime the coro
while True:
try:
try:
x = (yield)
except Exception as e: # This catches the SpamException
coro.throw(e)
else:
coro.send(x)
except StopIteration:
pass
Ini bekerja.
# Result
>> 0
>> 1
>> 2
***
>> 4
Tapi begitu juga ini!
def writer_wrapper(coro):
yield from coro
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 (seperti delegate
tetapi ditolak karena menambahkan kata kunci baru ke bahasa ini jauh lebih sulit daripada menggabungkan yang sudah ada.
Singkatnya, yang terbaik yield from
adalah transparent two way channel
antara penelepon dan sub-generator.
Referensi:
- PEP 380 - Sintaks untuk mendelegasikan ke sub-generator (Ewing) [v3.3, 2009-02-13]
- PEP 342 - Coroutine via Enhanced Generator (GvR, Eby) [v2.5, 2005-05-10]