Как устроены итераторы в Python
Во многих языках цикл for
не обходится без индексов. Например, в JavaScript, популярный способ обойти массив таков:
var books = ["Code Complete", "Programming Pearls", "The Mythical Man-Month"];
var booksArrayLength = books.length;
for (var i = 0; i < booksArrayLength; i++) {
console.log(myStringArray[i]);
}
Однако в Python мы делаем так:
for book in books:
print(book)
Такой более короткой записью мы обязаны итераторам. Далее мы разберемся, что это такое, как они работают и как сделать свой.
Как это устроено
Рассмотрим пример итерации по элементам списка:
>>> books = ["Code Complete", "Programming Pearls", "The Mythical Man-Month"]
>>> for book in books:
... print(book)
...
Code Complete
Programming Pearls
The Mythical Man-Month
Чтобы разобраться, как этот цикл работает, избавимся от оператора in
:
>>> books_iterator = iter(books)
>>> while True:
... try:
... book = next(books_iterator)
... except StopIteration:
... break
... print(book)
...
Code Complete
Programming Pearls
The Mythical Man-Month
Без оператора in
нам пришлось использовать встроенные функции iter
и next
. Мы к ним еще вернемся, а пока избавимся и от них:
>>> books_iterator = books.__iter__()
>>> while True:
... try:
... book = books_iterator.__next__()
... except StopIteration:
... break
... print(book)
...
Code Complete
Programming Pearls
The Mythical Man-Month
Теперь мы видим: у типа list
есть метод __iter__
, который возвращает объект.
Этот объект, в свою очередь, имеет метод __next__
, который возвращает, по одному, элементы списка
books
и поднимает StopIteration
, когда этих элементов больше нет. Еще этот объект тоже имеет метод
__iter__
, который возвращает его самого:
>>> books_iterator.__iter__() is books_iterator
True
Вместе __iter__
и __next__
составляют протокол итератора.
Так вот, объект класса, соблюдающего этот протокол, называется итератором (iterator).
Объект класса, который реализует метод __iter__
, называется итерируемым (iterable).
В данном случае books_iterator
– итератор, books
– итерируемое.
Роль метода __next__
заключается в том, чтобы задавать порядок обхода итерируемого
(не обязательно делать этот порядок строго от первого к последнему).
Метод __iter__
нужен для того, чтобы итератор и итерируемое могли использоваться с оператором in
:
>>> books_iterator = iter(books)
>>> for book in books_iterator:
... print(book)
...
Code Complete
Programming Pearls
The Mythical Man-Month
Встроенные функции iter
и next
вызывают методы __iter__
и __next__
, проделывая
при этом дополнительную работу. Например, iter
поднимает TypeError
, если __iter__
возвращает не итератор.
Как это сделать
Вот так выглядит итератор, который обходит список, начиная с последнего элемента и заканчивая первым:
class ReverseIt():
def __init__(self, reverse_me):
self.reverse_me = reverse_me
self.current_index = len(reverse_me) - 1
def __iter__(self):
return self
def __next__(self):
if self.current_index < 0:
raise StopIteration()
current_element = self.reverse_me[self.current_index]
self.current_index -= 1
return current_element
И вот так его можно использовать:
>>> reverse_books_iterator = ReverseIt(books)
>>> for book in reverse_books_iterator:
... print(book)
...
The Mythical Man-Month
Programming Pearls
Code Complete
Мы написали именно reverse_books_iterator = ReverseIt(books)
, а не reverse_books_iterator = iter(books)
.
Последнее вернет нам обычный итератор. Но мы могли бы так написать,
если бы у списка метод __iter__
был определен так:
def __iter__(self):
return ReverseIt(self)
Резюме
- Итератор – объект, у которого есть методы
__iter__
и__next__
. - Итерируемое – объект, у которого есть метод
__iter__
и он возвращает итератор. - Эти методы называют “магическими”, они часто используются в синтаксических конструкциях вроде
for ... in ...
.
Дальнейшее чтение
- Глава про итераторы в DiveIntoPython3;
- PEP 234 – документ, который предложил введение итераторов в Python;
- Определение итераторов в документации;
- Еще один способ использовать iter;
- Итераторы в JavaScript – “найди 10 отличий”.