TransWikia.com

Принцип работы yield в Python

Stack Overflow на русском Asked by devnull on December 25, 2020

Столкнулся с очень странным выводом при запуске кода:

def magic():
    raise RuntimeError
    print("bar")
    yield 100

print(magic())

и будет неожиданный для меня ответ:

<generator object gen at 0x7fb3073f0f68>

Вопрос немного обобщенный, хотелось бы понять как это реализовано в python и как понять на уровне кода отличия между обычной функцией и генератором?


потому как тип у генератор не отличается от обычной функции

def no_magic():
    pass

type(magic) is type(no_magic)

2 Answers

Схематично работа генератора выглядит так.

Функция возвращает собственно генератор, т.е. специальный объект, который хранит "текущее состояния" выполнения. Это состояние в себя включает место в функции генератора (т.е. команду yeild), где остановилось предыдущее выполнение.

При вызове next с параметром возвращенным вызовом magic() запускается собственно код magic и когда доходит до yield, то в этом месте значение, которое передано как аргумент в yield вернется из next. Вот пример с разметкой шагов, которые показывают порядок выполнения:

# я изменил функцию, чтобы продемонстрировать работу yield
def magic():
    yield 100  # шаг 3
    yield 200  # шаг 7
    print(1)   # шаг 11
    yield 300  # шаг 12
    print(2)   # шаг 16

gen = magic()  # шаг 1
a = next(gen)  # вызов next - шаг 2, присвоение - шаг 4
print(a)       # шаг 5, выведет в консоль 100
a = next(gen)  # вызов next - шаг 6, присвоение - шаг 8
print(a)       # шаг 9, выведет в консоль 200
a = next(gen)  # вызов next - шаг 10, присвоение - шаг 13
print(a)       # шаг 14, выведет в консоль 300
next(gen)      # вызов шаг 15, исключение StopIteration - шаг 17
               # т.к. magic закончила выполнение

Т.е. каждый раз при вызове next(gen) (а именно так происходит итерация по генератору) выполнение функции возобновляется с предыдущей точки yield и доходит до следущей, где происходит запоминание этой новой точки и возврат значения в вызывающую функцию.

Что касается типов, то тип magic и no_magic тот же - function. Важно что возвращает функция:

>>> type(magic())
<type 'generator'>
>>> type(no_magic())
<type 'NoneType'>

Сам код no_magic выполняется непосредственно в точке вызова no_magic(), у magic() этого не происходит, вызов отложен или еще по другому говорят, что вычисления делаются "лениво" или "по требованию"(lazy или on demand).

Correct answer by Roman Konoval on December 25, 2020

Описание:

Генераторы — это функции, которые можно приостанавливать и возобновлять во время их выполнения, при этом они возвращают объект, который можно итерировать. В отличие от списков, они ленивы и поэтому работают с текущим элемент только по запросу. Таким образом, они намного эффективнее используют память при работе с большими наборами данных.


Генераторы возвращают значение через оператор yield.

А так вы генератор вы создали через magic(), но вы его не выполнили.

Например так:

def magic():
    raise RuntimeError
    print("bar")
    yield 100

gen = magic()
print(gen)
print(next(gen))

Answered by gil9red on December 25, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP