Skip to content

Python: генераторы последний рубеж Часть 1 Предварительные сведения Генераторы и сопрограммы

Dmitry Ponyatov edited this page Oct 5, 2019 · 10 revisions

Python: генераторы последний рубеж

Часть 1 Предварительные сведения Генераторы и сопрограммы (рок)

Генераторы 101

  • оператор yield определяет функцию-генератор
def countdown(n):
    while n > 0:
        yield n
        n -= 1
  • Вы обычно используете его для питания итерации
for x in countdown(10):
    print('T-minus', x)
  • Простая, но элегантная идея

Под капотом

  • Объект генератора запускается в ответ на next()
>>> c = countdown(3)
>>> c
<generator object countdown at 0x10064f900>
>>> next(c)
3
>>> next(c)
2
>>> next(c)
1
>>> next(c)
Traceback (most recent call last):
 File "<stdin>", line 1, in ?
StopIteration
>>>
  • StopIteration возникает при возврате из функции

Интерлюдия

  • Генераторы как «итераторы» затеняют общую картину
  • Есть намного больше того, что можно yield

Генераторы как конвейер

  • Вложенные генераторы приводят к вычислительным конвейерам
  • Аналогично трубам в Unix
  • Невероятно полезные (см. предыдущие уроки)

Сопрограммы 101

  • yield также может получать значение
def receiver():
    while True:
        item = yield
        print('Got', item)
  • Он определяет генератор, которому вы отправляете данные
recv = receiver()
next(recv) # должен быть первый вызов для перехода на yield
recv.send('Hello')
recv.send('World')

Сопрограммы и поток данных

  • Сопрограммы реализуют обработку в идеологии потоков данных
  • Публикация / подписка, симуляция событий и т.д.

Фундамент

  • Оператор yield определяет функцию-генератор
def generator():
    ...
    ... yield ...
    ...
  • Достаточно присутствия yield в любом месте
  • Вызов функции создает экземпляр генератора
>>> g = generator()
>>> g
<generator object generator at 0x10064f120>
>>>

Продвижение генератора

  • next(gen) -- выполнение до следующего yield
  • возвращает полученный элемент данных (если он указан)
  • Это единственная разрешенная операция на вновь созданном генераторе
  • Примечание: То же, что и gen.__next__()

Отправка в генератор

  • gen.send(item) -- отправить элемент данных в генератор
def generator():
    ...
    item = yield
    ...
    ...
    yield value

g = generator()
next(g) # необходимый стартовый вызов
value = g.send(item)
  • Пробуждается на последнем yield, возвращает отправленное значение
  • Выполняется до следующего yield и отправляет значение

Закрытие генератора

  • gen.close() -- завершение генератора
def generator():
    ...
    try:
        yield
    except GeneratorExit:
        # Shutting down
        ...

g = generator()
next(g) # старт
g.close() # возбуждает исключение GeneratorExit
  • возбуждает исключение GeneratorExit на yield
  • Единственное разрешенное действие -- return
  • Если исключение не перехватывается, генератор молча завершает работу

Возбуждение исключений

  • gen.throw(typ [, val [,tb]]) бросает исключение
def generator():
    ...
    try:
        yield
    except RuntimeError as e:
        ...
    ...
    yield val

g = generator()
next(g) # Advance to yield
val = g.throw(RuntimeError,'Broken')
  • исключение попадает на yield
  • throw() возвращает следующее значение посланное yield (если оно указано)

Возвращаемые значения генератора

  • StopIteration возбуждается при выходе из генератора
  • Возвращаемое значение (если есть), передается как параметр исключения
  • Примечание: поведение только в Python 3 (в Python 2 генераторы не могут возвращать значения)

Делегация генератора

  • yield from gen делегация до вложенного подгенератора
  • Позволяет генераторам вызывать другие генераторы
  • Операции выполняются в текущем yield
  • Возвращаемое значение (если есть) возвращается

Пример делегирования

  • Цепочка итерируется вместе
def chain(x, y):
    yield from x
    yield from y
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> for x in chain(a, b):
... print(x,end=' ')
...
1 2 3 4 5 6
>>> c = [7,8,9]
>>> for x in chain(a, chain(b, c)):
... print(x, end=' ')
...
1 2 3 4 5 6 7 8 9
>>>

Мини-cправочник

  • Определение генератора
def generator():
    ...
    yield ...
    ...
    return result
  • Операции экземпляра генератора
gen = generator()
next(gen)               # инициализация
gen.send(item)          # послать данные
gen.close()             # завершение
gen.throw(exc, val, tb) # бросить исключение в генератор
result = yield from gen # делегация итерации
  • Используя их, вы можете сделать много полезных вещей

Python: генераторы последний рубеж Часть 2 А теперь что-то совсем другое

Clone this wiki locally