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

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

Стефан Кънев & Николай Бачийски

28.03.2007г.

За приятелите

Първа реализация

numbers = open('friends', 'r').readlines()
for number in numbers:
    mityo.invite(number.strip())

Втора реализация

numbers = open('friends')
while (True):
    number = numbers.readline()
    if not number:
        break
    mityo.invite(number.strip())

Трета реализация (любима)

for number in open('friends'):
    mityo.invite(number.strip())

Каква е магията?

итератор — обект, с метод next, който при всяко извикване или връща пореден елемент или вдига StopIteration, ако няма повече


генератор — обект, за който вградената функция iter връща итератор

Как работи for

for target in sequence:
    блок
  1. Опитва се да се добере то итератор
    1. извиква iter(sequence) — на практика проверява дали обекта ни има метод __iter__ и взима неговия резултат. Ако няма такова животно — отива на по-следващия слайд
    2. на всяка стъпка от цикъла в target слага резултата от next метода на получения по-горе обект
    3. … и така докато не прихване StopIteration

Пример

class MityoIter:
    def __init__(self, filename = 'friends'):
        self.numbers = open(filename)
    def next(self):
        number = self.numbers.readline()
        if not number:
            raise StopIteration
        return number
    def __iter__(self):
        return self
    
for number in MityoIter():
    mityo.invite(number.strip())

Как работи for (2)

for target in sequence:
    блок
  1. Цикли по стандартния начин:
    1. Проверява дали sequence поддържа оператора [] (__getitem__)
    2. Започвайки от индекс 0 се опитва да достъпи елементите един по един, като увеличава на всяка стъпка индекса с 1
    3. Свършва, когато прихване IndexError

Пример

class MityoBeard:
    def __init__(self, step = 1, maxage = 33):
        self.step = step
        self.maxage = maxage

    def immortalise(self):
        self.maxage = None

    def __getitem__(self, day):
        if self.maxage != None and day > self.maxage:
            raise IndexError("Mityo's beard is already gone.")
        return self.step*day
    
    beard = MityoBeard(2)
    for l in beard:
        print "Mityo's beard is %d cm. long." % l
    beard.immortalise()
    for l in beard:
        print "Mityo's beard is %d cm. long." % l

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

Генераторите обичат питоните

Примери

>>> list(enumerate(xrange(37, 43)))
[(0, 37), (1, 38), (2, 39), (3, 40), (4, 41), (5, 42)]
>>> map(len, MityoIter()) # map и подобните са умни и разбират
>>> partners = {'Pena': 'Mityo', 'Kuna': 'Mityo', 'Boca': 'Mityo'}
>>> partners.keys()
['Kuna', 'Boca', 'Pena']
>>> partners.iterkeys()

>>> iter(partners)

>>> for woman in partners:
    print "%s is with %s." % (woman, partners[woman])

Само вградените ли са лесни?

ша та yield-на

Магията не спира дотук:

def mityo_iter(filename='friends'):
    numbers = open(filename)
    while(True):
        number = numbers.readline()
        if number:
            yield number
        else:
            return
            
>>> mityo_iter # най-обикновена ф-я

>>> mityo_iter() # обикновена-чушки

>>> for number in mityo_iter():
        mityo.invite(number.strip())

Под капака

Функциите, които съдържат yield връщат генератори.


yield връща стойността на поредната стъпка, а итерацията завършва когато функцията свърши


yield приспива изпълнението на функцията и след това продължава от същото място


Подробен пример

def mityo_rabbits(n):
    x, y = 0, 1
    for i in xrange(n):
        yield y
        x, y = y, x + y

>>> list(mityo_rabits(7))
[1, 1, 2, 3, 5, 8, 13]
    

Още въпроси?