Итерируем все и вся
Насколько я успел понять по собственному опыту, при переходе на Python с другого языка программирования порой сложно привыкнуть к его специфическому подходу к циклам. Например, взять тот же самый for, который работает совершенно по-другому, нежели в других языках. Возьму на себя смелость рассказать о том, что мне самому поначалу было сложно осознать, а тем более использовать в своем коде — итераторы. Вещь на самом деле очень полезная, надо только уметь правильно ей пользоваться! ;)
АПД: Только сейчас заметил, что тема функционального программирования сегодня популярна как никогда :) Спасибо товарищу uj2 за раскрытие такой интересной темы, поддерживаю!
Итераторы — это специальные объекты, представляющие последовательный доступ к данным из контейнера. При этом немаловажную роль играет то, что память фактически не тратится, так как промежуточные данные выдаются по мере необходимости при запросе, поэтому фактически в памяти останутся только исходные данные и конечный результат, да и их можно читать и записывать, используя файл на диске.
Итератор удобно использовать вместе с последовательностью. Любой объект, поддерживающий интерфейс итератора, имеет метод next(), позволяющий переходить на следующую ступень вычисления. Чтобы получить итератор по объекту (например, по списку), к нему нужно применить функцию iter(). Кстати, уже упомянутый цикл for тоже задействует итератор, только без дополнительных танцев — все делается автоматически. Для обработки сложных наборов данных можно подключить стандартный модуль itertools.
Создадим простейший итератор вручную:
testIt = iter([1, 2, 3, 4, 5])
print [x for x in testIt]
Конечно, это простейшее использование цикла for, только записанное немного по-другому.
«И что же тут нового?» — спросите вы. Да, в принципе, ничего, просто немного залезли глубже в структуру обработки последовательностей. Но теперь, как вам такое: функция iter() может принимать не только структуру данных, которая так запросто представляет свой итератор, но и два совсем других аргумента: функцию без аргументов и стоповое значение, на котором итерация остановится. Пример:
def getSimple(state=[]):
if len(state) < 4:
state.append(" ")
return " "
testIt2 = iter(getSimple, None)
print [x for x in testIt2]
Пример основан на том, что в Python при отсутствии явного возвращения значения из функции возвращается значение None.
Теперь рассмотрим несколько функций, работа которых основана на итераторах:
enumerate()
Предназначена для нумерации элементов структуры данных (в том числе и другого итератора). Возвращает список кортежей, в каждом из которых первый элемент — номер (начиная с нуля), а второй — элемент исходного итератора. Пример:
>>> print [x for x in enumerate("abcd")]
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]
sorted()
Итератор, выполняющий сортировку:
>>> sorted('avdsdf')
['a', 'd', 'd', 'f', 's', 'v']
itertools.chain()
Итератор, позволяющий объединить два разных итератора в один. Пример:
from itertools import chain
it1 = iter([1,2,3])
it2 = iter([8,9,0])
for i in chain(it1, it2):
print i,
даст в результате «1 2 3 8 9 0».
itertools.count()
Итератор, выдающий бесконечные числа, начиная с заданного:
for i in itertools.count(1):
print i,
if i > 100:
break
Результат:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
96 97 98 99 100 101
itertools.cycle()
Бесконечно повторяет заданную последовательность:
tango = [1, 2, 3]
for i in itertools.cycle(tango):
print i,
Результат:
1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1..
Так же в этом модуле есть аналоги map(), starmap(), filter() и zip(), называются они, естественно, itertools.imap(), itertools.starmap(), itertools.ifilter() и itertools.izip(). Их преимущество в том, что, в отличие от своих «обычных» аналогов тратят гораздо меньше памяти. Существует также пара фильтров: itertools.takewhile() и itertools.dropwhile():
for i in takewhile(lambda x: x > 0, [1, -2, 3, -3]):
print i,
for i in dropwhile(lambda x: x > 0, [1, -2, 3, -3]):
print i,
Результат:
1
-2 3 -3
Дело в том, что takewhile() возвращает значения, пока условие истинно, а остальные значения даже не берет из итератора (именно не берет, а не высасывает все до конца!). И, наоборот, dropwhile() ничего не выдает, пока выполняется условие, зато потом выдает все без остатка.
Ну и, напоследок, давайте все-таки напишем свой собственный итератор (чуть было не дописал известную фразу Бендера)!
class Fibonacci:
"""Итератор последовательности Фибоначчи до N"""
def __init__(self, N):
self.n, self.a, self.b, self.max = 0, 0, 1, N
def __iter__(self):
# сами себе итератор: в классе есть метод next()
return self
def next(self):
if self.n < self.max:
a, self.n, self.a, self.b = self.a, self.n+1, self.b, self.a+self.b
return a
else:
raise StopIteration
# Использование:
for i in Fibonacci(100):
print i,
Вот, собственно и вся основная теория. Если хотите узнать побольше тонкостей — читайте специальную литературу. Если есть какие-то замечания — буду рад выслушать :)
В статье использованы материалы лекции Романа Арвиевича Сузи «Элементы функционального программирования».