Шаг 67.
Python: сборник рецептов.
Итераторы и генераторы. Определение генератора с дополнительным состоянием

    На этом шаге мы рассмотрим пример такого определения.

Задача

    Вы хотите написать генератор, но функция работает с дополнительным состоянием, которое вам хотелось бы каким-то образом показать пользователю.

Решение

    Если вам нужен генератор, который показывает пользователю дополнительное состояние, не забудьте, что вы можете легко реализовать его в форме класса, поместив код генератора в метод __iter__(). Например:

from collections import deque 
class linehistory:
    def __init__ (self, lines, histlen=3):
        self.lines = lines
        self.history = deque(maxlen=histlen)

    def __iter__(self):
        for lineno, line in enumerate(self.lines, 1): 
            self.history.append((lineno, line)) 
            yield line

    def clear(self):
        self.history.clear()

    Вы можете обращаться с этим классом так же, как с обычным генератором. Однако, поскольку он создает экземпляр, вы можете обращаться к внутренним атрибутам, таким как history или метод clear(). Например:

with open('somefile.txt') as f: 
    lines = linehistory(f) 
    for line in lines:
        if 'python' in line:
            for lineno, hline in lines.history:
                print('{}:{}'.format(lineno, hline), end='')


Обсуждение

    С генераторами легко попасть в ловушку, если пытаться делать все только с помощью функций. В результате может получиться сложный код, если генератору нужно взаимодействовать с другими частями программы некими необычными способами (раскрытие атрибутов, разрешение на управление через вызов методов и т. п.). В этом случае просто используйте определение класса, как показано выше. Определение генератора в методе __iter__() не изменит ничего в том, как вы напишете алгоритм. Но тот факт, что генератор станет частью класса, упростит задачу предоставления пользователям атрибутов и методов для каких-то взаимодействий.

    Потенциальная хрупкость показанного приема заключается в том, что он может потребовать дополнительного шага: вызова iter(), если вы собираетесь провести итерацию не через цикл for. Например:

>>> f = open('somefile.txt')
>>> lines = linehistory(f)
>>> next(lines)
Traceback (most recent call last):
  .   .   .   .
TypeError: 'linehistory' object is not an iterator
>>> # Сначала вызываем iter(), затем начинаем итерирование 
>>> it = iter(lines)
>>> next(it)
'hello world\n'
>>> next(it)
'this is a test\n'
>>>

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




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