Получить значение из вложенной генераторной функции, возвращённое с помощью оператора "return"
Вот мой код:
def gen_middle():
def gen_nested():
counter = 0
while True:
yield counter
counter += 1
if counter >= 2:
return 'My special value'
yield from gen_nested()
def main():
iterator = iter(gen_middle())
try:
while True:
value = next(iterator)
print(value)
except StopIteration as exc:
# returned value (with "return") from gen_nested was expected
print(exc.value)
main()
Я ожидал вывод:
0
1
My special value
Но получил:
0
1
None
В связи с этим у меня два вопроса.
- Оператор
yield from
не эскалирует исключениеStopIteration
со значением из вложенного генератора? - Могу ли я получить это значение (
'My special value'
) не меняя код функцийgen_middle
иgen_nested
?
Ответы (1 шт):
Все высокоуровневые итерирующие конструкции (от for
до yield from
) сами обрабатывают StopIteration
, наружу "выходит" уже новое исключение StopIteration
, а то что было брошено самым внутренним итератором со своим value
наружу не выходит. Наверное, логично было бы, чтобы value
пробрасывалось через всю цепочку отловленных StopIteration
, но видимо это такой редкий случай (use case), что никто про это не подумал.
С другой стороны, если внутри было несколько итераторов (например, происходит склейка нескольких последовательностей значений, как в itertools.chain), то какое из значений должно пробрасываться наружу? Если последнее, то почему оно, почему должны пропасть return
значения других генераторов?
Тут проще всего создать свое отдельное исключение, изнутри его выбрасывать, на самом внешнем уровне обрабатывать, тогда и внешний while-next
можно заменить на обычный for
(что более красиво):
class MyCustomException(Exception):
def __init__(self, value):
self.value = value
def gen_middle():
def gen_nested():
counter = 0
while True:
yield counter
counter += 1
if counter >= 2:
raise MyCustomException('My special value')
yield from gen_nested()
def main():
iterator = iter(gen_middle())
try:
for value in iterator:
print(value)
except MyCustomException as exc:
print(exc.value)
main()
Конкретно для случая, если внутри функции ровно один yield from
, можно его тупо заменить на return
, чтобы просто возвращался исходный генератор:
class MyCustomException(Exception):
def __init__(self, value):
self.value = value
def gen_middle():
def gen_nested():
counter = 0
while True:
yield counter
counter += 1
if counter >= 2:
return 'My special value'
return gen_nested() # было yield from
def main():
iterator = iter(gen_middle())
try:
while True:
value = next(iterator)
print(value)
except StopIteration as exc:
# returned value (with "return") from gen_nested was expected
print(exc.value)
main()