Здесь следует заметить, что обычная функция map нормально воспринимает итераторы в любом сочетании с итерабельными (поддающимися итерациям) объектами:
Листинг
for i in map(lambda x, y: (x,y), iter([1,2]), [1,2,3]):
print i,
(1, 1) (2, 2) (None, 3)
Функция itertools.starmap подобна itertools.imap, но имеет всего два аргумента. Второй аргумент — последовательность кортежей, каждый кортеж которой задает набор параметров для функции (первого аргумента):
Листинг
>>> from itertools import starmap
>>> for i in starmap(lambda x, y: str(x) + y, [(1,'a'), (2,'b')]):
… print i,
…
1a 2b
Функция ifilter работает как filter. Кроме того, в модуле itertools есть функция ifilterfalse, которая как бы добавляет отрицание к значению функции:
Листинг
for i in ifilterfalse(lambda x: x > 0, [1, — 2, 3, — 3]):
print i,
— 2–3
Функции itertools.takewhile и itertools.dropwhile
Некоторую новизну вносит другой вид фильтра: takewhile и его «отрицательный» аналог 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 ничего не выдает, пока выполняется условие, зато потом выдает все без остатка.
Функция itertools.izip
Функция izip аналогична встроенной zip, но не тратит много памяти на построение списка кортежей, так как итератор выдает их строго по требованию.
Функция itertools.groupby
Эта функция дебютировала в Python 2.4. Функция принимает два аргумента: итератор (обязательный) и необязательный аргумент — функцию, дающую значение ключа: groupby(iterable[, func]). Результатом является итератор, который возвращает двухэлементный кортеж: ключ и итератор по идущим подряд элементам с этим ключом. Если второй аргумент опущен, элемент итератора сам является ключом. В следующем примере группируются идущие подряд положительные и отрицательные элементы:
Листинг
import itertools, math
lst = map(lambda x: math.sin(x*.4), range(30))
for k, i in itertools.groupby(lst, lambda x: x > 0):
print k, list(i)
Функция itertools.tee
Эта функция тоже появилась в Python 2.4. Она позволяет клонировать итераторы. Первый аргумент — итератор, подлежащий клонированию. Второй (N) — количество необходимых копий. Функция возвращает кортеж из N итераторов. По умолчанию N=2. Функция имеет смысл, только если итераторы задействованы более или менее параллельно. В противном случае выгоднее превратить исходный итератор в список.
Собственный итератор
Для полноты описания здесь представлен пример итератора, определенного пользователем. Если пример не очень понятен, можно вернуться к нему после изучения объектно–ориентированного программирования:
Листинг
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,
Простые генераторы
Разработчики языка не остановились на итераторах. Как оказалось, в интерпретаторе Python достаточно просто реализовать простые генераторы. Под этим термином фактически понимается специальный объект, вычисления в котором продолжаются до выработки очередного значения, а затем приостанавливаются до возникновения необходимости в выдаче следующего значения. Простой генератор формируется функцией–генератором, которая синтаксически похожа на обычную функцию, но использует специальный оператор yield для выдачи следующего значения. При вызове такая функция ничего не вычисляет, а создает объект с интерфейсом итератора для получения значений. Другими словами, если функция должна возвращать последовательность, из нее довольно просто сделать генератор, который будет функционально эквивалентной «ленивой» реализацией. Ленивыми называются вычисления, которые откладываются до самого последнего момента, когда получаемое в результате значение сразу используется в другом вычислении.
Для примера с последовательностью Фибоначчи можно построить такой вот генератор:
Листинг
def Fib(N):
a, b = 0, 1
for i in xrange(N):
yield a
a, b = b, a + b
Использовать его не сложнее, чем любой другой итератор:
Листинг
for i in Fib(100):
print i,
Однако следует заметить, что программа в значительной степени упростилась.
Генераторное выражение
В Python 2.4 по аналогии со списковым включением появилось генераторное выражение. По синтаксису оно аналогично списковому, но вместо квадратных скобок используются круглые. Списковое включение порождает список, а, значит, можно ненароком занять очень много памяти. Генератор же, получающийся в результате применения генераторного выражения, списка не создает, он вычисляет каждое следующее значение строго по требованию (при вызове метода next).