Давайте спочатку вийдемо з речі. Пояснення, 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, якщо ви не впевнені, що навіть означає передача даних в генератор , вам потрібно спочатку все скинути і прочитати про супроводи - вони дуже корисні (на відміну від підпрограм ), але, на жаль, менш відомі в Python. Цікавий курс Дейва Бізлі з питань судових процедур - чудовий початок. Прочитайте слайди 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
Це працює, і ми усунули один рядок коду. І, напевно, наміри трохи чіткіші (чи ні). Але нічого життя не змінюється.
Надсилання даних в генератор (супровід), використовуючи вихід із - Частина 1
Тепер давайте зробимо щось цікавіше. Створимо підпрограму під назвою, writer
яка приймає надіслані їй дані та записує в сокет, 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
Обгортка повинна прийняти дані, які надсилаються до неї (очевидно), а також повинна обробляти, StopIteration
коли цикл for 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
прозоро ручки посилаючи значення або кидати значення в суб-генератора.
Однак це все ще не стосується всіх кутових справ. Що станеться, якщо зовнішній генератор закритий? Що щодо випадку, коли підгенератор повертає значення (так, в 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 - Контроль за допомогою посилених генераторів (GvR, Eby) [v2.5, 2005-05-10]