Trước tiên hãy lấy một thứ ra khỏi đường đi. Lời giải thích yield from g
tương đương với việc for v in g: yield v
thậm chí không bắt đầu thực hiện công lý cho yield from
tất cả những gì về. Bởi vì, hãy đối mặt với nó, nếu tất cả đều yield from
mở rộng for
vòng lặp, thì nó không đảm bảo thêm yield from
vào ngôn ngữ và ngăn chặn toàn bộ các tính năng mới được triển khai trong Python 2.x.
Có gì yield from
không là nó thiết lập một kết nối hai chiều trong suốt giữa người gọi và sub-máy phát điện :
Kết nối là "trong suốt" theo nghĩa là nó cũng sẽ truyền bá mọi thứ một cách chính xác, không chỉ các yếu tố được tạo ra (ví dụ: ngoại lệ được lan truyền).
Kết nối là "hai chiều" theo nghĩa là dữ liệu có thể được gửi từ và đến một máy phát.
( Nếu chúng ta đang nói về TCP, yield from g
có thể có nghĩa là "bây giờ tạm thời ngắt kết nối ổ cắm của máy khách của tôi và kết nối lại với ổ cắm máy chủ khác này". )
BTW, nếu bạn không chắc chắn việc gửi dữ liệu đến máy phát thậm chí có nghĩa là gì, bạn cần bỏ mọi thứ và đọc về coroutines trước khi chúng rất hữu ích (tương phản chúng với chương trình con ), nhưng không may là ít được biết đến trong Python. Khóa học tò mò của Dave Beazley về Coroutines là một khởi đầu tuyệt vời. Đọc các slide 24-33 để có một đoạn mồi nhanh.
Đọc dữ liệu từ máy phát điện sử dụng năng suất từ
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
Thay vì lặp lại bằng tay reader()
, chúng ta có thể chỉ cần yield from
nó.
def reader_wrapper(g):
yield from g
Điều đó hoạt động, và chúng tôi đã loại bỏ một dòng mã. Và có lẽ ý định rõ ràng hơn một chút (hoặc không). Nhưng không có gì thay đổi cuộc sống.
Gửi dữ liệu đến máy phát điện (coroutine) bằng cách sử dụng năng suất từ - Phần 1
Bây giờ hãy làm điều gì đó thú vị hơn. Hãy tạo một coroutine được gọi là writer
chấp nhận dữ liệu được gửi đến nó và ghi vào ổ cắm, fd, v.v.
def writer():
"""A coroutine that writes data *sent* to it to fd, socket, etc."""
while True:
w = (yield)
print('>> ', w)
Bây giờ câu hỏi là, hàm bao bọc xử lý việc gửi dữ liệu tới người viết như thế nào, sao cho bất kỳ dữ liệu nào được gửi đến trình bao bọc được gửi trong suốt đến 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
Trình bao bọc cần chấp nhận dữ liệu được gửi đến nó (rõ ràng) và cũng nên xử lý StopIteration
khi vòng lặp for hết. Rõ ràng chỉ làm for x in coro: yield x
sẽ không làm. Đây là một phiên bản hoạt động.
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
Hoặc, chúng ta có thể làm điều này.
def writer_wrapper(coro):
yield from coro
Điều đó tiết kiệm 6 dòng mã, làm cho nó dễ đọc hơn nhiều và nó chỉ hoạt động. Ma thuật!
Gửi dữ liệu đến năng suất của trình tạo từ - Phần 2 - Xử lý ngoại lệ
Hãy làm cho nó phức tạp hơn. Nếu nhà văn của chúng ta cần xử lý các trường hợp ngoại lệ thì sao? Giả sử writer
tay cầm a SpamException
và nó in ***
nếu gặp phải.
class SpamException(Exception):
pass
def writer():
while True:
try:
w = (yield)
except SpamException:
print('***')
else:
print('>> ', w)
Nếu chúng ta không thay đổi writer_wrapper
thì sao? Nó có hoạt động không? Hãy thử
# 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
Ừm, nó không hoạt động vì x = (yield)
chỉ làm tăng ngoại lệ và mọi thứ đều dừng lại. Hãy làm cho nó hoạt động, nhưng xử lý thủ công các ngoại lệ và gửi chúng hoặc ném chúng vào trình tạo phụ ( 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
Những công việc này.
# Result
>> 0
>> 1
>> 2
***
>> 4
Nhưng điều này cũng vậy!
def writer_wrapper(coro):
yield from coro
Các yield from
xử lý minh bạch gửi các giá trị hoặc ném các giá trị vào các tiểu phát điện.
Điều này vẫn không bao gồm tất cả các trường hợp góc mặc dù. Điều gì xảy ra nếu máy phát bên ngoài bị đóng? Còn trường hợp khi trình tạo phụ trả về một giá trị (có, trong Python 3.3+, các trình tạo có thể trả về giá trị), thì giá trị trả về nên được lan truyền như thế nào? Điều đó yield from
minh bạch xử lý tất cả các trường hợp góc là thực sự ấn tượng . yield from
chỉ làm việc kỳ diệu và xử lý tất cả những trường hợp.
Cá nhân tôi cảm thấy yield from
là một lựa chọn từ khóa kém bởi vì nó không làm cho bản chất hai chiều rõ ràng. Có những từ khóa khác được đề xuất (thích delegate
nhưng bị từ chối vì việc thêm một từ khóa mới vào ngôn ngữ khó hơn nhiều so với việc kết hợp các từ khóa hiện có.
Nói tóm lại, nó là tốt nhất để nghĩ về yield from
như một transparent two way channel
giữa người gọi và sub-máy phát điện.
Người giới thiệu:
- PEP 380 - Cú pháp ủy quyền cho trình tạo phụ (Ewing) [v3.3, 2009/02/13]
- PEP 342 - Coroutines thông qua Máy phát điện nâng cao (GvR, Eby) [v2,5, 2005-05-10]