ก่อนอื่นเรามาทำความเข้าใจก่อน คำอธิบายที่yield from g
เทียบเท่าfor v in g: yield v
ไม่ได้เริ่มก่อให้เกิดความยุติธรรมกับสิ่งที่yield from
เป็นอยู่ เพราะเรามาดูกันถ้าทุกอย่างyield from
นั้นขยายfor
ลูปไปแล้วมันก็ไม่รับประกันว่าyield from
จะเพิ่มในภาษาและห้ามไม่ให้มีฟีเจอร์ใหม่ ๆ มากมายที่จะนำมาใช้ใน Python 2.x
สิ่งที่yield from
ไม่สามารถจะสร้างการเชื่อมต่อแบบสองทิศทางโปร่งใสระหว่างโทรและย่อยกำเนิด :
การเชื่อมต่อนั้น "โปร่งใส" ในแง่ที่ว่ามันจะเผยแพร่ทุกอย่างถูกต้องด้วยเช่นกันไม่ใช่แค่องค์ประกอบที่สร้างขึ้น (เช่นมีการเผยแพร่ข้อยกเว้น)
การเชื่อมต่อคือ "สองทิศทาง" ในแง่ที่ว่าข้อมูลสามารถส่งได้ทั้งจากและไปยังเครื่องกำเนิด
( ถ้าเรากำลังพูดถึง TCP yield from g
อาจหมายถึง "ตอนนี้ยกเลิกการเชื่อมต่อซ็อกเก็ตของลูกค้าของฉันชั่วคราวและเชื่อมต่อกับซ็อกเก็ตเซิร์ฟเวอร์อื่น" )
BTW หากคุณไม่แน่ใจว่าการส่งข้อมูลไปยังเครื่องกำเนิดไฟฟ้านั้นหมายความว่าอย่างไรคุณต้องทิ้งทุกอย่างและอ่านเกี่ยวกับcoroutinesก่อน - มันมีประโยชน์มาก (ตรงกันข้ามกับรูทีนย่อย ) แต่น่าเสียดายที่ไม่ค่อยรู้จักใน Python หลักสูตร Curious ของ Dave Beazley เกี่ยวกับ Coroutinesเป็นการเริ่มต้นที่ยอดเยี่ยม อ่านสไลด์ 24-33เพื่อไพรเมอร์ด่วน
การอ่านข้อมูลจากเครื่องกำเนิดไฟฟ้าโดยใช้ผลผลิตจาก
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
แทนการทำซ้ำมากกว่าreader()
เราก็สามารถyield from
มัน
def reader_wrapper(g):
yield from g
ใช้งานได้และเราตัดรหัสหนึ่งบรรทัดออก และอาจเป็นเจตนาที่ชัดเจนเล็กน้อย (หรือไม่) แต่ไม่มีอะไรเปลี่ยนแปลงชีวิต
การส่งข้อมูลไปยังเครื่องกำเนิดไฟฟ้า (coroutine) โดยใช้อัตราผลตอบแทนจาก - ส่วนที่ 1
ตอนนี้ขอทำสิ่งที่น่าสนใจมากขึ้น ลองสร้าง coroutine ที่เรียกwriter
ว่ารับข้อมูลที่ส่งไปและเขียนลงใน socket, fd และอื่น ๆ
def writer():
"""A coroutine that writes data *sent* to it to fd, socket, etc."""
while True:
w = (yield)
print('>> ', w)
ตอนนี้คำถามคือวิธีการที่ควรกระดาษห่อจับฟังก์ชั่นการส่งข้อมูลไปยังนักเขียนเพื่อให้ข้อมูลใด ๆ ที่ถูกส่งไปยังเสื้อคลุมจะโปร่งใสส่งไปยัง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
wrapper ต้องยอมรับข้อมูลที่ถูกส่งไป (ชัด) และควรจัดการStopIteration
เมื่อวง for หมดแล้ว เห็นได้ชัดว่าเพียงแค่ทำfor x in coro: yield x
จะไม่ทำ นี่คือรุ่นที่ใช้งานได้
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
หรือเราสามารถทำได้
def writer_wrapper(coro):
yield from coro
ที่บันทึกรหัส 6 บรรทัดทำให้อ่านได้มากขึ้นและใช้งานได้ มายากล!
การส่งข้อมูลไปยังตัวสร้างผลตอบแทนจาก - ส่วนที่ 2 - การจัดการข้อยกเว้น
มาทำให้มันซับซ้อนขึ้น เกิดอะไรขึ้นถ้านักเขียนของเราต้องจัดการกับข้อยกเว้น? สมมติว่าwriter
ด้ามจับ a SpamException
และมันพิมพ์ออกมา***
ถ้ามันเจอ
class SpamException(Exception):
pass
def writer():
while True:
try:
w = (yield)
except SpamException:
print('***')
else:
print('>> ', w)
ถ้าเราไม่เปลี่ยนwriter_wrapper
ล่ะ ใช้งานได้หรือไม่ มาลองกัน
# 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
อืมมันไม่ทำงานเพราะx = (yield)
เพียงยกข้อยกเว้นและทุกอย่างจะหยุดชะงัก ขอให้มันทำงาน แต่ตนเองจัดการข้อยกเว้นและส่งพวกเขาหรือการขว้างปาลงในเครื่องกำเนิดไฟฟ้าย่อย ( 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
วิธีนี้ใช้ได้ผล
# Result
>> 0
>> 1
>> 2
***
>> 4
แต่ทำอย่างนี้!
def writer_wrapper(coro):
yield from coro
yield from
จับโปร่งใสส่งค่าหรือการขว้างปาค่าลงย่อยกำเนิด
นี้ยังไม่ครอบคลุมทุกมุมกรณีแม้ว่า จะเกิดอะไรขึ้นถ้าเครื่องกำเนิดไฟฟ้าด้านนอกปิด? สิ่งที่เกี่ยวกับกรณีที่ sub-generator ส่งกลับค่า (ใช่ใน Python 3.3+, กำเนิดสามารถคืนค่า), วิธีการส่งกลับค่าควรจะเผยแพร่? ที่yield from
โปร่งใสจัดการทุกกรณีมุมเป็นที่น่าประทับใจจริงๆ yield from
เพียงใช้งานได้อย่างน่าอัศจรรย์และจัดการกับทุกกรณีเหล่านั้น
โดยส่วนตัวฉันรู้สึกว่าyield from
เป็นตัวเลือกคำหลักที่ไม่ดีเพราะมันไม่ได้ทำให้ลักษณะสองทางชัดเจน มีการเสนอคำหลักอื่น ๆ (เช่นdelegate
แต่ถูกปฏิเสธเนื่องจากการเพิ่มคำหลักใหม่ในภาษานั้นยากกว่าการรวมคำที่มีอยู่เดิม
โดยสรุปเป็นวิธีที่ดีที่สุดที่จะคิดว่าyield from
เป็นตัวเรียกtransparent two way channel
ระหว่างและตัวสร้างย่อย
อ้างอิง:
- PEP 380 - ไวยากรณ์สำหรับการมอบหมายให้ผู้สร้างย่อย (Ewing) [v3.3, 2009-02-13]
- PEP 342 - Coroutines ผ่าน Enhanced Generators (GvR, Eby) [v2.5, 2005-05-10]