На выражение, стоящее после for x in
, питон автоматически напускает функцию iter
. Она возвращает объект - итератор. Существуют и выражения-итераторы. Они выглядят как генераторы списков, но пишутся в круглых скобках, а не в квадратных. Сравним следующие 2 примера:
s=0
for n in [i**2 for i in range(1000)]:
s+=n
s
s=0
for n in (i**2 for i in range(1000)):
s+=n
s
В первом случае в памяти создаётся список из 1000 элементов. Во втором в памяти хранится только короткое выражение - итератор. Оно выдаёт очередные члены последовательности по одному, по мере надобности.
Посмотрим, как работает такое выражение.
it=(i**2 for i in range(4) if i!=2)
it
next(it)
next(it)
next(it)
next(it)
Итераторы могут использоваться не только в циклах. Есть много функций с аргументами - итераторами.
max((10*x-x**2 for x in range(10)))
Функция min
аналогична. В таких случаях, когда выражение - итератор является единственных аргументом функции, заключать его в скобки не обязательно.
sum(10*x-x**2 for x in range(10))
Часто хочется применить какую-нибудь функцию к каждому элементу последовательности. Это делает функция map
, она возвращает объект map
, который тоже является итератором.
def f(x):
return x**2
m=map(f,[0,1,2])
m
list(m)
Часто бывает нужна какая-нибудь очень простая функция. Не хочется придумывать для неё имя, которое будет использовано всего 1 раз, и засорять пространство имён. В таких случаях лучше использовать анонимную функцию:
list(map(lambda x:2*x,[0,1,2]))
Анонимные функции записываются так:
f=lambda x,y:x+2*y
f
Их, естественно, можно вызывать:
f(1,2)
К сожалению, только очень простые функции можно записать в виде анонимных - они должны состоять из одного единственного выражения. Для многострочных функций это невозможно.
Ещё одна полезная функция - filter
, она позволяет отфильтровать последовательность, оставив в ней только те элементы, которые удовлетворяют некоторому условию.
list(filter(lambda x:x>0,[0,1,-2,3,-4]))
Выражения-итераторы позволяют задавать только довольно простые последовательности. Значительно более широкие возможности предоставляют функции-генераторы. Они выглядят как функции, в которых вместо return
используется yield
.
def gen():
yield 0
yield -1
yield 4
Вызвав такую функцию, мы получим некоторый объект, являющийся итератором.
it=gen()
it
Его можно использовать любым обычным образом.
for x in it:
print(x)
Вызвав функцию gen
снова, мы получим новый итератор, который опять можно использовать.
it=gen()
list(it)
При первом вызове next
операторы функции выполняются до первого yield
. Возвращается указанное в нём значение; текущее состояние функции (точка выполнения, значения локальных переменных) запоминается. При следующем вызове next
выполнение продолжается с того же места до тех пор, пока опять не встретится yield
. Когда выполнение дойдёт до конца (или до return
), выдаётся исключение StopIteration
.
it=gen()
next(it)
next(it)
next(it)
next(it)
Много интересных функций для работы с итераторами имеется в модуле itertools
стандартной библиотеки.
from itertools import repeat,count,islice,cycle,chain,accumulate
Вызов repeat(x)
возвращает итератор, повторяющий значение x
до бесконечности; repeat(x,n)
повторяет его n
раз.
list(repeat('abc',3))
Бесконечные итераторы могут использоваться для написания циклов, выход из которых производится по break
; они также полезны как аргументы различных операций над итераторами. Одна из таких операций - islice
: islice(it,n)
- это итератор, возвращающий первые n
элементов итератора it
, а islice(it,n,m)
возвращает элементы с n
-ного (включительно) до m
-го (не включая его).
list(islice([0,1,4,9],2))
list(islice([0,1,4,9],1,3))
Вызов count()
возвращает итератор, выдающий бесконечную последовательность 0, 1, 2, ...; count(n)
- начиная с n
; count(n,h)
- с шагом h
.
list(islice(count(),10))
list(islice(count(4),10))
list(islice(count(4,2),10))
Вызов cycle(it)
возвращает итератор, выдающий элементы it
по циклу до бесконечности (для этого, разумеется, итератор it
должен быть конечным; в противном случае мы никогда не доберёмся до конца первого цикла).
list(islice(cycle('ку'),10))
Вызов chain(i1,i2)
возвращает итератор, выдающий сначала все элементы i1
, а затем все элементы i2
. Аргументов может быть и $>2$. Разумеется, если среди аргументов встретится бесконечный итератор, то до его конца мы никогда не доберёмся.
for i in chain([0,1],[4,9]):
print(i)
Есть и несколько встроенных функций для работы с итераторами, их не нужно импортировать из itertools
. Так, zip
работает следующим образом:
list(zip([0,1],[4,9]))
Он прекращает работу, когда закончится более короткая последовательность. Аргументов может быть и $>2$.
list(zip(count(),[1,2,4],'abcdefgh'))
Отсюда видно, что enumerate(x)
, который мы уже обсуждали, эквивалентен zip(count(),x)
.
Из числовой последовательности $x_0$, $x_1$, $x_2$, ... можно построить последовательность кумулятивных сумм $s_0=x_0$, $s_1=s_0+x_1$, $s_2=s_1+x_2$, ...
list(islice(accumulate(count(1)),10))
Вместо сложения можно использовать любую функцию 2 переменных.
list(islice(accumulate(count(1),lambda x,y:x*y),10))
Кстати, вместо этого lambda
выражения мы могли бы использовать функцию mul
, которую надо импортировать из модуля operator
. Там есть и add
, и другие инфиксные операции в виде функций, так что их можно использовать как фактические параметры в вызовах.
Функция reduce
из модуля functools
стандартной библиотеки фактически возвращает последний элемент последовательности $s_i$, то есть $(((x_0+x_1)+x_2)+x_3)$... Вместо сложения может использоваться любая функция 2 переменных.
from functools import reduce
from operator import add
reduce(add,[1,4,9,16])
С помощью итераторов можно делать поразительные вещи. Вот, например, бесконечная последовательность простых чисел (методом решета Эратосфена).
def primes():
yield 2
d={}
for q in count(3,2):
p=d.pop(q,None)
if p is None: # q простое
d[q**2]=q
yield q
else: # q составное
x=q+2*p
while x in d:
x+=2*p
d[x]=p
list(islice(primes(),10))
Можно написать класс, объекты которого являются итераторами. Сначала напишем простой класс, реализующий метод __iter__
. Объект такого класса можно использовать в циклах for
и других контекстах, где пребуется итератор. Там на этот объект напускается встроенная функция iter
, эквивалентная вызову метода __iter__
. Этот метод должен возвращать объект-итератор.
class List:
def __init__(self,l):
self.l=list(l)
def __iter__(self):
return ListIter(self.l)
Теперь напишем класс ListIter
. Он реализует метод __next__
, который вызывается, когда на объект этого класса напускается встроенная функция next
. Объект хранит номер текущего лемента в списке; __next__
возвращает следующий элемент, а если его нет, возбуждает исключение StopIteration
. Из одного объекта класса List
можно создать сколько угодно объектов класса ListIter
, которые будут пробегать по этому списку независимо друг от друга.
class ListIter:
def __init__(self,l,n=0):
self.l=l
self.n=n-1
def __next__(self):
if self.n>=len(self.l)-1:
raise StopIteration()
else:
self.n+=1
return self.l[self.n]
l=List('xyz')
it=iter(l)
next(it)
next(it)
next(it)
next(it)
Как мы уже обсуждали, объект класса List
является итерируемым, т.е. его можно использовать в for
цикле. Там не него напускается функция iter
, а на получившийся объект класса ListIter
- функция next
, пока он не возбудит исключение StopIteration
.
for x in l:
print(x)
Можно совместить эти два класса.
class List2:
def __init__(self,l):
self.l=list(l)
self.n=-1
def __iter__(self):
return self
def __next__(self):
if self.n>=len(self.l)-1:
raise StopIteration()
else:
self.n+=1
return self.l[self.n]
l=List2('abc')
for x in l:
print(x)
Но в этом случае невозможно создать несколько независимых итераторов по одному списку.