Итератори и генератори

„ Програмиране с Python“, ФМИ

def lecturers():
   yield "Стефан Кънев"
   yield "Николай Бачийски"
   yield "Точо Точев"
   yield "Димитър Димитров"
   yield "Еди Ведър"

32.03.2009 г.

Какво е итератор?

iter (noun, genitive itineris); n, third declension

  1. A journey or march.
  2. A road.

Итераторите в Python

Обект с метод __next__(), който:

Обикновено не викате obj.__next__(), а ползвате вградената next(obj), която го прави вместо вас

iter()

Вградената функция, която приема един аргумент и връща итератор за него. Работи както очаквате за вградети типове - list, tuple.

>>> i = iter([1, 2])
>>> next(i)
1
>>> next(i)
2
>>> next(i)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Как работи for?

for target in sequence:
    блок
  1. Изпълнява iter(sequence) за да вземе итератор
  2. Изпълнява next() върху него. Прекратява ако той предизвика StopIteration.
  3. Насочва target към върната стойност и изпълнява блок
  4. Повтаря от стъпка 2

iter() за наши обекти

Ако искаме наш обект да може да бъде итериран чрез for имаме избор:

__iter__

Ако изберете този подход, __iter__ трябва да връща обект, който има метод __next__(). Вече знаете как той трябва да работи

__iter__ (2)

class Primes:
    def __init__(self, limit):
        self.limit = limit
        self.last = 1

    def __next__(self):
        while self.limit > self.last:
            self.last += 1
            if all([self.last % n for n in range(2, self.last)]):
                return self.last
        raise StopIteration

    def __iter__(self): return self

print(list(Primes(13)))

__getitem__

Ако не сте дефинирали __iter__, тогава функцията iter() ще провери дали вашия обект няма __getitems__. Ако да, тя сама конструира итератор, работещ така:

  1. Достъпва индексите един по един, започвайки от 0 и увеличавайки на всяка стъпка с 1
  2. Спира когато прихване IndexError.

__getitem__ (2)

class Vector(object):
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def __getitem__(self, index):
        if index == 0: return self.x
        elif index == 1: return self.y
        elif index == 2: return self.z
        else: raise IndexError

Generator expression

squares = (n ** 2 for n in range(100000000))
for square in squares:
    print(square)
    if square > 1000: break

Обратно към итераторите

Някои врътки

Структурата на итераторите ни позволява…

Но:

Има, разбира се!

It's evolution, baby!

def no_comment(limit):
    for number in range(2, limit + 1):
        if all(number % i for i in range(2, number)):
            yield number

to13 = no_comment(13)
print(next(to13))
print(next(to13))
print(next(to13))

Магията обяснена

Друг пример

def flat(collection):
    for element in collection:
        if isinstance(element, (list, tuple)):
            for x in flat(element): yield x
        else:
            yield element

print(list(flat([1, 2, [3, 4, 5], 5, [6, [7, [8, 9], 10], 11]])))

Зайци

def fib():
    x, y = 0, 1
    while(True):
        yield y
        x, y = y, x + y

Още въпроси?