Python 2.3: рассмотрим itertools
Язык программирования Python уже достиг версии 2.3. Справиться о современном состоянии можно на сайте https://www.python.org .
Одно из свежих добавлений -- модуль itertools, который включает в себя набор функций для построения итераторов. Но в начале разберемся, что же такое итератор. Не секрет, что итерация, то есть повторение вычислительного процесса -- цикл -- является одним из фундаментальных блоков, из которых строятся императивные программы. В языке Python итерации можно производить с использованием циклов ДЛЯ (for), ПОКА (while), а также в списковых включениях -- новом методе описания списков, появившемся в Python 2.x.
В языке Python итерация часто производится с использованием некоторого параметра, который пробегает все значения из некоторой последовательности или, в более общем случае -- итерабельного (способного к итерации) объекта. Именно так происходит при использовании оператора for или спискового включения. Следующий пример иллюстрирует итерацию по символам строки:
Здесь c является параметром цикла.for c in "example": print c
В следующем примере итерация производится по последовательности целых чисел от 1 до 10. Для порождения такой последовательности в Python имеется специальная встроенная функция range():
Конечно, с помощью range() использовать большие последовательности неудобно: они занимают много памяти, поэтому в Python имеется функция xrange(), которая в описании цикла for ведет себя также, как и range(), но реального списка не создает.s = 0 for i in range(1, 11): s = s + i**2 print c
Аналогично можно рассмотреть методы readlines() и xreadlines() файлового объекта. В первом случае из файла читаются все строки и записываются в память в виде списка строк, а во втором строки читаются из файла по мере необходимости. Следующий пример показывает применение функции xreadlines():
Примеры с xrange() и xreadlines() являются примерами функций, которые возвращают объекты-итераторы: специальные объекты, призванные выдавать очередное значение по требованию. В версиях Python 2.1 и ниже такие объекты можно было создавать с помощью описания довольно хитрого класса. Вот простейший пример, в котором класс Fibonacci описывает объекты для получения последовательности чисел Фибоначчи:f = open("file.txt") for l in f.xreadlines(): print len(l)
Итераторы в Python можно встретить повсюду, где раньше были списки. Например, ключи, значения и пары ключей-значений теперь можно получить в виде итератора:class Fibonacci: """Прообраз итератора последовательности Фибоначчи""" def __init__(self, max): self.n, self.a, self.b, self.max = 0, 0, 1, max def __getitem__(self, x): 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 IndexError # Пример выполнения for i in Fibonacci(10): print i,
То есть, теперь нет нужды создавать отдельный (подчас огромный) список на основе словаря -- значения без труда в нужный момент (just-in-time, так сказать) выдаст итератор!d = {1: 'a', 2: 'b', 3: 'c'} for k, v in d.iteritems(): print k, v 1 a 2 b 3 c
Нужно пронумеровать объекты списка или другого итерабельного объекта? Теперь есть встроенная функция enumerate():
В Python 2.2 появился специальный интерфейс для итераторов, благодаря чему итераторы стало возможным описывать единообразно. Тот же пример с использованием стандартного интерфейса:for (n, v) in enumerate("example"): print n, ":", v 0 : e 1 : x 2 : a 3 : m 4 : p 5 : l 6 : e
Заметьте, что по сравнению с предыдущим примером произошли три изменения. Во-первых, добавился метод __iter__(), который возвращает объект-итератор (в нашем примере -- сам объект). Во вторых, вместо метода __getitem__(), через который цикл for получал значения последовательности, применен метод next(), который является необходимым для объекта-итератора в Python. В-третьих, теперь в качестве сигнала завершения итераций используется исключение StopIteration.class Fibonacci: """Итератор последовательности Фибоначчи""" def __init__(self, max): self.n, self.a, self.b, self.max = 0, 0, 1, max def __iter__(self): 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
В Python также появилась встроенная функция iter(), которая создает итератор для некоторой последовательности. Пример:
Конечно, в данном случае итератор нужен не был, так как оператор for прекрасно работает с последовательностями.s = [1, 2, 3, 4, 5] siter = iter(s) for i in siter: print i
Есть и другая форма функции iter(), которая в качестве первого аргумента принимает функцию без аргументов, а в качестве второго аргумента -- стоповое значение.
В следующем примере происходит ввод и суммирование чисел. Итерации останавливаются при вводе числа 0:
Кстати, построчное чтение из файла можно теперь записать так:s = 0 for i in iter(input, 0): s += i print s
Итераторы применяются в нескольких стандартных модулях Python. Например, с помощью finditer() из модуля re легко получить в обычном цикле for все куски строки, соответствующие регулярному выражению:f = open("file.txt") for l in iter(f.readline, ""): print l # делаем что-то с очередной строкой файла
Перейдем теперь к рассмотрению функций модуля itertools, который появился в Python 2.3b1.for i in re.finditer("[0-9]+", "12 3 1 23 fff 1678"): print int(i.group())
Функция chain() позволяет сделать итератор, состоящий из нескольких итераторов. Итераторы задаются в виде отдельных аргументов. Пример:
Даст:from itertools import chain it1 = iter([1,2,3]) it2 = iter([8,9,0]) for i in chain(it1, it2): print i,
Функция repeat() строит итератор, повторяющий некоторый элемент заданное количество раз:1 2 3 8 9 0
Бесконечный итератор, дающий целые числа, начиная с заданного:for i in itertools.repeat(1, 4): print i, 1 1 1 1
Можно бесконечно повторять и некоторую последовательность (или значения другого итератора) с помощью функции cycle():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
Аналогами map() и filter() в модуле itertools найдутся imap() и ifilter(). Отличие imap от map в том, что для преждевременно завершившихся итераторов None не подставляется. Пример: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() нормально воспринимает итераторы в любом сочетании с итерабельными (поддающимися итерациям) объектами:for i in map(lambda x, y: (x,y), [1,2], [1,2,3]): print i, (1, 1) (2, 2) (None, 3) from itertools import imap for i in imap(lambda x, y: (x,y), [1,2], [1,2,3]): print i, (1, 1) (2, 2)
Есть и еще одна разновидность imap() -- starmap():for i in map(lambda x, y: (x,y), iter([1,2]), [1,2,3]): print i, (1, 1) (2, 2) (None, 3)
Надеемся, из этой записи понятно, в чем ее суть.for i in itertools.starmap(lambda x, y: x+y, [(1,2),(2,3),(3,4)]): print i, 3 5 7
Аналогично ifilter() работает как filter(). Кроме того, в модуле itertools есть функция ifilterfalse(), которая как бы добавляет отрицание к значению функции-первого аргумента:
Некоторую новизну вносит другой вид фильтра: takewhile() и его "отрицательный" аналог dropwhile(). Следующий пример поясняет их принцип действия:for i in ifilterfalse(lambda x: x > 0, [1, -2, 3, -3]): print i, -2 -3
То есть, takewhile() дает значения пока условие истинно, а остальные значения не берет из итератора (именно не берет, а не высасывает все до конца!). И, наоборот, dropwhile() ничего не выдает, пока выполняется условие, зато потом выдает все без остатка.for i in takewhile(lambda x: x > 0, [1, -2, 3, -3]): print i, print for i in dropwhile(lambda x: x > 0, [1, -2, 3, -3]): print i, 1 -2 3 -3
Функция izip() аналогична встроенной zip(), но не тратит много памяти на построение списка кортежей, так как итератор выдает их строго по требованию.
В модуле itertools есть функция islice(), которая возвращает итератор, который выбирает из итерабельного объекта только значения с заданными индексами:
В заключение хочется отметить, что итераторы -- это очень практичное продолжение славного функционально-программного начала в языке Python. Итераторы по сути позволяют организовать так называемые "ленивые" (lazy) вычисления, при которых значения вычисляются только когда они непосредственно требуются.for i in islice(range(100), 10, 23, 2): print i, 10 12 14 16 18 20 22
Post Scriptum:
А вы знаете, что в Python есть еще и генераторы? Наш пример с Фибоначчи упрощается во много раз, если мы применим простой генератор (simple generator):Но об этом в следующий раз.def Fibonacci(max): a, b = 0, 1 for i in xrange(max): yield a a, b = b, a + b for i in Fibonacci(10): print i,
Источники:
python.onego.ru