събота, 17 януари 2009 г.

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


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

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

28.03.2007г.

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

  • Митьо Питона не е много паметлив.
  • Пази телефонните номера на приятелите си в прост текстов файл:

    062600400
    +1800500
    061830647
    062633733
  • Една вечер Митьо решил да организира джакузи-парти и трябвало да се обади на всичките си приятели
  • За целта трябвало да прочете всички номера от файла и да им се обади един по един
  • Не забравяйте: Митьо не е много паметлив — може да помни само един номер в главата си

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

numbers = open('friends', 'r').readlines()
for number in numbers:
mityo.invite(number.strip())
  • open отваря файл и връща файлов обект
  • readlines прочита целия файл и ни връща списък от низове (заедно с новите редове)
  • strip маха белите полета от двата края на низа (+ новите редове)
  • Виждате ли проблем с тази реализация?
  • Памет: четем целия файл в на Митьо паметта

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

numbers = open('friends')
while (True):
number = numbers.readline()
if not number:
break
mityo.invite(number.strip())
  • readline чете един ред от файла, ако няма — връща празен низ
  • Виждате ли проблем с тази реализация?
  • Простота: то не са while-ове, то не са if-ове

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

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

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

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

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

  • range vs. xrange
  • dict.keys vs. dict.iterkeys vs. dict
  • os.walk
  • enumerate
  • sorted
  • list(генератор)

Примери

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

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

  • вградените генератори се ползват лесно
  • досега не видяхме лесен начин да си правим сами — само с класове и __iter__
  • е да, ама не

ша та 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]

Няма коментари:

Публикуване на коментар