Шаг 64.
Python: сборник рецептов. Итераторы и генераторы. Создание новых итерационных паттернов с помощью генераторов

    На этом шаге мы рассмотрим общий способ их создания.

Задача

    Вы хотите реализовать собственный паттерн итераций, который будет отличаться от обычных встроенных функций (таких как range(), reversed() и т. п.).

Решение

    Если вы хотите реализовать новый тип итерационного паттерна, определите его с помощью генератора. Вот, например, генератор, который создает диапазон чисел с плавающей точкой:

>>> def frange(start, stop, increment):
	x = start
	while x < stop:
		yield x
		x += increment

		
>>> 

    Чтобы использовать такую функцию, вы должны проитерировать по ней в цикле или применить ее с какой-то другой функцией, которая потребляет итерируемый объект (например, sum(), list() и т. п.). Например:

>>> for n in frange(0, 4, 0.5):
	print(n)

	
0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
>>> list(frange(0, 1, 0.125))
[0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875]
>>> 


Обсуждение

    Само присутствие инструкции yield в функции превращает ее в генератор. В отличие от обычной функции, генератор запускается только в ответ на итерацию. Вот эксперимент, который вы можете провести, чтобы понять внутренний механизм работы таких функций:

>>> def countdown(n):
	print('Starting to count from', n)
	while n > 0:
		yield n
		n -= 1
	print('Done!')

	
>>> # Создает генератор - обратите внимание на отсутствие вывода
>>> c = countdown(3)
>>> c
<generator object countdown at 0x0000020BE3E16740>
>>> # Выполняется до первого yield и выдает значение
>>> next(c)
Starting to count from 3
3
>>> # Выполняется до следующего yield
>>> next(c)
2
>>> # Выполняется до следующего yield
>>> next(c)
1
>>> # Выполняется до следующего yield (итерирование останавливается)
>>> next(c)
Done!
Traceback (most recent call last):
  .   .   .   .
    next(c)
StopIteration
>>>

    Ключевая особенность функции-генератора состоит в том, что она запускается только в ответ на операции next в ходе итерирования. Когда генератор возвращает значение, итерирование останавливается. Однако цикл for, который обычно используется для выполнения итераций, сам заботится об этих деталях, поэтому в большинстве случаев вам не стоит волноваться о них.

    На следующем шаге мы рассмотрим реализацию протокола итератора.




Предыдущий шаг Содержание Следующий шаг