Итераторы
Когда вы говорите "for i in container:", в Python происходит не магия, а некий набор действий. Ясно, что идея конструкции такая, что у вас есть контейнер с последовательно идущими данными, а по ним бежит что-то вроде курсора, или, еще говорят, итератор. Что именно там происходит?
Чтобы объект container можно было использовать в конструкции "for i in container", от него требуется, чтоб он имел метод iter(), возвращающий некий объект-итератор:
От этого объекта - iterator - также требуется наличие у него 2 методов: next() и iter(). Зачем нужен первый - станет ясно сразу, про второй - попозже, это наворот.
Работа цикла "for i in container: print i;" эквивалентна следующей программе:
То есть сначала вызов метода iter() контейнера создает итератор, а затем к нему постоянно применяется метод next(), который что-то возвращает в переменную i, пока итератор не истощится, а в таком случае он должен выбросить исключение StopIteration, которое будет отловлено и остановит исполнение цикла.
Пример программы, создающей контейнер и итератор:
1 class MyIterator(object):
2 def __init__(self, value):
3 self.value=value
4 def next(self):
5 if self.value>0:
6 self.value-=1
7 return self.value
8 else:
9 raise StopIteration
10 def __iter__(self):
11 return self
12
13 class MyContainer(object):
14 def __init__(self):
15 self.a=10
16 def __iter__(self):
17 return MyIterator(self.a)
Контейнеры-итераторы
У итераторов есть метод iter(), который просто возвращает сам итератор. В примере выше это была просто формальность. Реально это нужно, чтобы можно было сделать объект-контейнер, который был бы сам себе и итератором:
1 class TenLittleNiggers(object):
2 def __init__(self):
3 self.rhyme=[]
4 self.rhyme.append("""Ten little nigger boys went out to dine;
5 One choked his little self, and then there were nine.""")
6 self.rhyme.append("""Nine little nigger boys sat up very late;
7 One overslept himself, and then there were eight.""")
8 self.rhyme.append("""Eight little nigger boys travelling in Devon;
9 One said he'd stay there, and then there were seven.""")
10 self.rhyme.append("""Seven little nigger boys chopping up sticks;
11 One chopped himself in half, and then there were six.""")
12 self.rhyme.append("""Six little nigger boys playing with a hive;
13 A bumble-bee stung one, and then there were five.""")
14 self.rhyme.append("""Five little nigger boys going in for law;
15 One got in chancery, and then there were four.""")
16 self.rhyme.append("""Four little nigger boys going out to sea;
17 A red herring swallowed one, and then there were three.""")
18 self.rhyme.append("""Three little nigger boys walking in the Zoo;
19 A big bear hugged one, and then there were two.""")
20 self.rhyme.append("""Two little nigger boys sitting in the sun; One got frizzled up, and then there was one.""")
21 self.rhyme.append("""One little nigger boy living all alone;
22 He got married, and then there were none.""")
23 def __iter__(self):
24 return self
25 def next(self):
26 try:
27 return self.rhyme.pop(0)
28 except:
29 raise StopIteration
1 >>> niggers=TenLittleNiggers()
2 >>> for i in niggers:
3 ... print i
4 ...
5
6 Ten little nigger boys went out to dine;
7 One choked his little self, and then there were nine.
8 Nine little nigger boys sat up very late;
9 One overslept himself, and then there were eight.
10 Eight little nigger boys travelling in Devon;
11 One said he'd stay there, and then there were seven.
12 Seven little nigger boys chopping up sticks;
13 One chopped himself in half, and then there were six.
14 Six little nigger boys playing with a hive;
15 A bumble-bee stung one, and then there were five.
16 Five little nigger boys going in for law;
17 One got in chancery, and then there were four.
18 Four little nigger boys going out to sea;
19 A red herring swallowed one, and then there were three.
20 Three little nigger boys walking in the Zoo;
21 A big bear hugged one, and then there were two.
22 Two little nigger boys sitting in the sun; One got frizzled up, and then there was one.
23 One little nigger boy living all alone;
24 He got married, and then there were none.
25 >>> print niggers.next()
26
27 Traceback (most recent call last):
28 File "<pyshell#92>", line 1, in <module>
29 print niggers.next()
30 File "<pyshell#83>", line 29, in next
31 raise StopIteration
32 StopIteration
Тут гибридный объект niggers сначала возвращает сам себя при вызове его iter() из "for i in niggers:", а потом по нему же делаются проходы последовательными вызовами next(). У такого гибрида есть побочный эффект: он одноразовый. То есть после исполнения одного for по нему, повторно вызвать его уже будет невозможно (ну точнее, он выбросит StopIteration Exception).
Функция iter()
Есть такая встроенная функция iter(), которая в основном непонятно зачем нужна. Она может быть в 2 формах: В основной форме ей подается объект-контейнер с методом iter(), а она возвращает итератор. Что более интересно, у iter() есть еще одна форма: ей можно подать callable-объект (к примеру, функцию) первым аргументом, а вторым - т.н. метку. При этом iter() создаст итератор, который, при вызове его next(), будет исполнять наш callable-объект без аргументов, но если callable вернет значение, равное метке, выбросит StopIteration:
Генераторы
В Python можно делать функции, которые могут остановиться при выполнении, передать управление вызвавшей их программе, которая затем может попросить эту прерванную функцию продолжить работу с того же места. Такие функции называются генераторными функциями (generator functions) и вместо выражения return в точке выхода содержат выражение yield:
Кроме того, при вызове такой функции выдается не значение, а объект - итератор (в данном случае называемый генератором), последовательные вызовы метода next() которого и будут заставлять генераторную функцию продолжить исполнение до следующего yield:
Еще у итератора-генератора есть забавный метод send(), который позволяет ему не только выдавать значения основной программе, но и получать их:
Здесь метод send() возобновляет исполнение программы после третьего yield - того, что в строке value = (yield value). В этой строке сначала исполняется yield value, и только на следующей итерации (yield value) заменяется на переданное методом send() значение 10, соответственно value становится равна 10 и выдается следующим yield. Кстати, если вместо send() скомандовать next(), (yield value) заменится на None.
Убийца мозга - алгоритм перечисления всех простых чисел
Вот эта вот рекурсивная и углубляющаяся жуть перечисляет все простые числа от одного и до исчерпания динамической памяти.
Генераторные выражения
Также Python поддерживает "ленивую" форму генераторов - generator expressions - что-то наподобие list comprehensions. Подразумевается, что выражение слева от for будет вычисляться и выдаваться yield:
Модуль itertools
Это модуль, который содержит итераторы (например, itertools.count(), который перечисляет натуральные числа отсюда и до горизонта), а также функцию tee(), которая позволяет сделать копию итератора где-то посреди процесса извлечения им элементов.