Програмиране с Python: Итератори и генератори
Author: Стефан Кънев, Николай Бачийски и Димитър Димитров

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

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

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

#### 16.04.2008

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

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

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

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

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

- Връща следващата стойност
- Предизвиква изключение `StopIteration`

# iter()

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

    >>> i = iter([1, 2])
    >>> i.next()
    1
    >>> i.next()
    2
    >>> i.next()
    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` имаме избор:

- Предефинираме `__getitem__`
- Предефинираме `__iter__`

# __iter__

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

# __iter__ (2)

    class FileIter(object):
        def __init__(self, file):
            self.file = file

        def next(self):
            while(True):
                next_line = self.file.readline()
                if not next_line:
                    raise StopIteration
                if not next_line.startswith("#"):
                    return next_line

        def __iter__(self): return self

    print FileIter

    <class '__main__.FileIter'>

# __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

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

- итерацията е нещо съвсем отнесено
- не винаги можем да разберем лесно дали използваме списък или итератор
- в повечето случаи не ни е нужно да знаем
- от многото знания боли глава

# Някои врътки

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

- …да не произвеждаме елемент докато не ни е потрябвал (мързел)
- …да произвеждаме безкрайни последователности

Но:

- Дефинирането на такива неща е тегаво
- Няма ли по-лесен начин?

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

# It's evolution, baby!

    def no_comment(file):
        while(True):
            line = file.readline()
            if not line:
                return
            if not line.startswith("#"):
                yield line

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

- Функциите, съдържащи `yield` се наричат генератори. Те са по-особени.
- Като изпълните генератор, той връща специален обект, притежаващ метод `next()`.
- Като извикате `next()`, генератора се изпълнява като нормална функция.
- Като се стигне до `yield`, изпълнението на функцията замръзва и `next()` връща стойността предадена на `yield`.
- При следващи извиквания на `next()` върху обекта-генератор функцията продължава от същото място по същия начин.
- Когато изпълнението излезе от функцията-генератор, `next()` вдига `StopIteration`

# Друг пример

    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]]))

    [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

# Още въпроси?

- Страница на курса - http://fmi.py-bg.net/
- Форуми на курса - http://fmi.py-bg.net/forums/
- http://pearljam.com/