Как устроены итераторы в 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 отличий”.
Попробуйте бесплатные уроки по Python
Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.
Переходите на страницу учебных модулей «Девмана» и выбирайте тему.
Тип данных Iterator, протокол итератора в Python
Тип iterator является обобщением понятия последовательности. Объект считается итерируемым, если он физически является последовательностью, либо он является объектом, который воспроизводит по одному результату за раз в контексте инструментов выполнения итераций, таких как цикл for . in .
В некотором смысле в категорию итерируемых объектов входят как физические последовательности, это списки, кортежи, диапазоны и т. д. , так и виртуальные последовательности, которые вычисляются по требованию.
Недостаток типа Iterator состоит в том, что при первом его вызове вычисляются сразу все значения последовательности как физической, так и виртуальной, к тому же все они хранятся в памяти до их исчерпания. Этот недостаток решает тип generator (генератор).
Чтобы обеспечить поддержку итерации, для объектов контейнера необходимо обязательно определить один метод container.__iter__() . Этот метод возвращает объект итератора. Объект необходим для поддержки протокола итератора, описанного ниже. Если контейнер поддерживает различные типы итераций, можно предоставить дополнительные методы для конкретного запроса итераторов для этих типов итераций.
Примером объекта, поддерживающего несколько форм итерации, может служить древовидная структура, поддерживающая обход как по ширине, так и по глубине. Метод container.__iter__() соответствует слоту tp_iter структуры типа для объектов Python в Python/C API.
Ограничения итератора.
- Нельзя получить длину итератора функцией len() ;
>>> it = iter([i*i for i in range(10)]) >>> len(it) # Traceback (most recent call last): # File "", line 1, in # TypeError: object of type 'list_iterator' has no len()
>>> it = iter([i*i for i in range(10)]) >>> it[2] # Traceback (most recent call last): # File "", line 1, in # TypeError: 'list_iterator' object is not subscriptable
>>> it = iter([i*i for i in range(10)]) >>> it[2:5] # Traceback (most recent call last): # File "", line 1, in # TypeError: 'list_iterator' object is not subscriptable >>> import itertools >>> list(itertools.islice(it, 2, 5)) # [49, 64, 81]
>>> it = iter([i*i for i in range(10)]) >>> list(it) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> list(it) # []
Реализация/протокол типа Iterator :
Объекты типа Iterator должны поддерживать следующие два метода, которые вместе образуют протокол итератора :
container.__iter__() :
Метод вернет объект итератора. Это необходимо для того, чтобы разрешить использование контейнеров и итераторов с операторами for и in . Метод container.__iter__() соответствует слоту tp_iter структуры типа для объектов Python в API-интерфейсе. PyTypeObject.tp_iter — необязательный указатель на функцию, которая возвращает итератор для объекта. Его присутствие обычно сигнализирует о том, что экземпляры этого типа являются итеративными, хотя последовательности могут быть итерируемыми без этой функции.
container.__next__() :
Метод должен возвращать следующий элемент из контейнера, а если элементы в последовательности закончились, то метод container.__next__() должен бросить исключение StopIteration .
Python определяет несколько объектов итератора для поддержки итерации по общим и конкретным типам последовательностей, словарям и другим более специализированным формам. Конкретные типы не важны за пределами их реализации протокола итератора.
Как только метод container.__next__() итератора вызывает StopIteration , он должен продолжать вызывать его и при последующих вызовах. Реализации, которые не подчиняются этому свойству, считаются нарушенными.
Для создания объекта типа Iterator можно воспользоваться встроенной функцией iter() . По итератору можно двигаться с помощью функции next() .
Пример создания итератора Iterator :
class SimpleIterator: def __iter__(self): return self def __init__(self, limit): self.limit = limit self.counter = 0 def __next__(self): if self.counter self.limit: ret = self.counter self.counter += 1 return ret else: raise StopIteration iters = SimpleIterator(5) # По итератору можно двигаться с помощью функции next() print('Функция next:', next(iters)) print('Функция next:', next(iters)) for i in iters: print('Цикл for . in: ', i) # Вызовем исключение `StopIteration` # т.к. итерация закончилась в цикле for next(iters) # Функция next: 0 # Функция next: 1 # Цикл for . in: 2 # Цикл for . in: 3 # Цикл for . in: 4 # Traceback (most recent call last): # File "/home/script/Simple-Iterator.py", line 26, in # next(iters) # File "/home/script/Simple-Iterator.py", line 15, in __next__ # raise StopIteration # StopIteration
Понимание того, как работают итераторы и генераторы в языках программирования, это один из первых шагов к освоению последовательной обработки гигантских потоков данных. Трейдинг и технический анализ, это вещи, на которых многие делают целое состояние.
- КРАТКИЙ ОБЗОР МАТЕРИАЛА.
- Утиная типизация ‘Duck Typing’
- Что такое вызываемый объект callable?
- Как проверить тип переменной/объекта
- Логический тип данных bool
- Целые числа int
- Ограничение длины преобразования целочисленной строки
- Вещественные числа float
- Комплексные числа complex
- Типы последовательностей
- Список list
- Кортеж tuple
- Диапазон range
- Текстовые строки str
- Словарь dict
- Множество set и frozenset
- Итератор Iterator, протокол итератора
- Генератор generator и выражение yield
- Контекстный менеджер with
- Байтовые строки bytes
- Байтовый массив bytearray
- Тип memoryview, буфер обмена
- Файловый объект file object
- Универсальный псевдоним GenericAlias
- Объект объединения Union
Итераторы и итерируемые объекты в Python. Методы __next__ и __iter__
В английской документации по Python фигурируют два похожих слова – iterable и iterator. Обозначают они разное, хотя и имеющее между собой связь.
На русский язык iterable обычно переводят как итерируемый объект, а iterator – как итератор, или объект-итератор. С объектами обоих разновидностей мы уже сталкивались в курсе «Python. Введение в программирование», однако не делали на этом акцента.
Iterable и iterator – это не какие-то конкретные классы-типы, наподобие int или list . Это обобщения. Существует ряд встроенных классов, чьи объекты обладают возможностями iterable. Ряд других классов порождают объекты, обладающие свойствами итераторов.
Кроме того, мы можем сами определять классы, от которых создаются экземпляры-итераторы или итерируемые объекты.
Примером итерируемого объекта является список. Примером итератора – файловый объект. Список включает в себя все свои элементы, а файловый объект по-очереди «вынимает» из себя элементы и «забывает» то, что уже вынул. Также не ведает, что в нем содержится еще, так как это «еще» может вычисляться при каждом обращении или поступать извне. Например, файловый объект не знает сколько еще текста в связанном с ним файле.
Из такого описания должно быть понятно, почему один и тот же список мы можем перебирать сколько угодно раз, а с файловым объектом это можно сделать только единожды.
Зачем нужны объекты, элементы которых можно получить только один раз? Представьте, что текстовый файл большой. Если сразу загрузить его содержимое в память, то последней может не хватить. Также бывает удобно генерировать значения на лету, по требованию, если они нужны в программе только один раз. В противовес тому, как если бы они были получены все сразу и сохранены в списке.
У всех итераторов, но не итерируемых объектов, есть метод __next__. Именно его код обеспечивает выдачу очередного элемента. Каков этот код, зависит от конкретного класса. У файлового объекта это по всей видимости код, читающий очередную строку из связанного файла.
>>> f = open('text.txt') >>> f.__next__() 'one two\n' >>> f.__next__() 'three \n' >>> f.__next__() 'four five\n' >>> f.__next__() Traceback (most recent call last): File "", line 1, in StopIteration
Когда итератор выдал все свои значения, то очередной вызов __next__() должен возбуждать исключение StopIteration . Почему именно такое исключение? Потому что на него «реагирует» цикл for . Для for это сигнал останова.
Судя по наличию подчеркиваний у __next__ , он относится к методам перегрузки операторов. Он перегружает встроенную функцию next() . То есть когда объект передается в эту функцию, то происходит вызов метода __next__() этого объекта-итератора.
>>> f = open('text.txt') >>> next(f) 'one two\n' >>> next(f) 'three \n' >>> next(f) 'four five\n' >>> next(f) Traceback (most recent call last): File "", line 1, in StopIteration
Если объект итератором не является, то есть у него нет метода __next__ , то вызов функции next() приведет к ошибке:
>>> a = [1, 2] >>> next(a) Traceback (most recent call last): File "", line 1, in TypeError: 'list' object is not an iterator
Внутренний механизм работы цикла for так устроен, что на каждой итерации он вызывает функцию next() и передает ей в качестве аргумента объект, указанный после in в заголовке. Как только next() возвращает StopIteration , цикл for ловит это исключение и завершает свою работу.
Напишем собственный класс с методом __next__ :
>>> class A: . def __init__(self, qty): . self.qty = qty . def __next__(self): . if self.qty > 0: . self.qty -= 1 . return '+' . else: . raise StopIteration . >>> a = A(3) >>> next(a) '+' >>> next(a) '+' >>> next(a) '+' >>> next(a) Traceback (most recent call last): File "", line 1, in File "", line 9, in __next__ StopIteration
Вызов next() работает, но если мы попробуем передать объект циклу for , получим ошибку:
>>> b = A(5) >>> for i in b: . print(i) . Traceback (most recent call last): File "", line 1, in TypeError: 'A' object is not iterable
Интерпретатор говорит, что объект типа A не является итерируемым объектом. Другими словами, цикл for ожидает, что после in будет стоять итерируемый объект, а не итератор. Как же так, если цикл for потом вызывает метод __next__() , который есть только у итераторов?
На самом деле цикл for ожидает, что у объекта есть не только метод __next__ , но и __iter__ . Задача метода __iter__ – «превращать» итерируемый объект в итератор. Если в цикл for передается уже итератор, то метод __iter__() этого объекта должен возвращать сам объект:
>>> class A: . def __init__(self, qty): . self.qty = qty . def __iter__(self): . return self . def __next__(self): . if self.qty > 0: . self.qty -= 1 . return '+' . else: . raise StopIteration . >>> a = A(4) >>> for i in a: . print(i) . + + + +
Если циклу for передается не итератор, а итерируемый объект, то его метод __iter__() должен возвращать не сам объект, а какой-то объект-итератор. То есть объект, созданный от другого класса.
Получается, в классах-итераторах метод __iter__ нужен лишь для совместимости. Ведь если for работает как с итераторами, так и итерируемыми объектами, но последние требуют преобразования к итератору, и for вызывает __iter__() без оценки того, что ему передали, то требуется, чтобы оба – iterator и iterable – поддерживали этот метод. С точки зрения наличия в классе метода __iter__ итераторы можно считать подвидом итерируемых объектов.
Очевидно, по аналогии с next() , цикл for вызывает не метод __iter__() , а встроенную в Python функцию iter() .
Если список передать функции iter() , получим совсем другой объект:
>>> s = [1, 2] >>> si = iter(s) >>> type(s) >>> type(si) >>> si >>> next(si) 1 >>> next(si) 2 >>> next(si) Traceback (most recent call last): File "", line 1, in StopIteration
Как видно, объект класса list_iterator исчерпывается как нормальный итератор. Список s при этом никак не меняется. Отсюда понятно, почему после обхода циклом for итерируемые объекты остаются в прежнем составе. От них создается «копия»-итератор, а с ними самими цикл for не работает.

Напишем свой iterable-класс и связанный с ним iterator-класс, чтобы проиллюстрировать, как в Python может быть реализована взаимосвязь между итерируемым объектом и его итератором.
class Letters: def __init__(self, string): self.letters = [] for i in string: self.letters.append(f'--') def __iter__(self): return LettersIterator(self.letters[:]) class LettersIterator: def __init__(self, letters): self.letters = letters def __iter__(self): return self def __next__(self): if self.letters == []: raise StopIteration item = self.letters[0] del self.letters[0] return item kit = Letters('aeoui') print(kit.letters) for i in kit: print(i) print(kit.letters)
['-a-', '-e-', '-o-', '-u-', '-i-'] -a- -e- -o- -u- -i- ['-a-', '-e-', '-o-', '-u-', '-i-']
Заметим, что если в программе определяется класс, от которого будут создаваться итерируемые объекты, то в этой же программе должен быть класс, от которого создаются итераторы для этих объектов. При этом сам по себе класс, порождающий итераторы, никакой ценности может не иметь в том смысле, что непосредственно от него создание экземпляров в основной ветке программы не предполагается.
Обратное не верно. Если нужен класс, от которого создаются итераторы, определять класс для неких связанных с ним итерируемых объектов не требуется.
Практическая работа
Напишите класс-итератор, объекты которого генерируют случайные числа в количестве и в диапазоне, которые передаются в конструктор.
Курс с примерами решений практических работ:
pdf-версия
X Скрыть Наверх
Объектно-ориентированное программирование на Python
Итераторы в Python
Концепция итераторов никоим образом не специфична для Python. В самом общем виде это объект, который используется для перебора в цикле последовательности элементов. Однако разные языки программирования реализуют данную концепцию по-разному или не реализуют вовсе. В Python каждый цикл for использует итератор, в отличие от многих других языков. В данной статье мы поговорим про итераторы в Python. Кроме того, мы рассмотрим итерируемые объекты (англ. iterables) и т.н. nextables.
Итерируемые объекты
Итерируемый объект – это объект, который можно проитерировать, т.е. пройтись по элементам объекта в цикле. Например, итерируемым объектом может быть список или кортеж. В Python, чтобы объект был итерируемым, он должен реализовывать метод __iter__ . Этот метод-болванка принимает в качестве входных данных только экземпляр объекта — self — и должен возвращать объект-итератор. Вы можете использовать встроенную функцию iter , чтобы получить итератор итерируемого объекта.
Обратите внимание, что итерируемый объект не обязательно является итератором. Поскольку на самом деле сам по себе он не выполняет итерацию. У вас может быть отдельный объект-итератор, который возвращается из итерируемого класса, а не класс, обрабатывающий свою собственную итерацию. Но об этом позже.
Итераторы
Перейдем к собственно итераторам, рабочей лошадке итерации (особенно в Python). Итераторы – это уровень абстракции, который инкапсулирует знания о том, как брать элементы из некоторой последовательности. Мы намеренно объясняем это в общем виде, поскольку «последовательность» может быть чем угодно, от списков и файлов до потоков данных из базы данных или удаленного сервиса. В итераторах замечательно то, что код, использующий итератор, даже не должен знать, какой источник используется. Вместо этого он может сосредоточиться только на одном, а именно: «Что мне делать с каждым элементом?».
Итерация без итератора
Чтобы лучше понять преимущества итераторов, давайте кратко рассмотрим итерацию без итераторов. Примером итерации без итератора является классический цикл for в стиле C. Этот стиль существует не только в C, но и, например, в C++, go и JavaScript.
Пример того, как это выглядит в JavaScript:
let numbers = [1,2,3,4,5] for(let i=0; i < numbers.length; i++)< // Extract element const current_number = numbers[i] // Perform action on element const squared = current_number ** 2 console.log(squared) >
Здесь мы видим, что данный тип цикла for должен работать как с извлечением, так и с действиями для каждого элемента.
Все циклы for в Python используют итераторы
В Python нет циклов for в стиле C. А циклы for в Python-стиле напоминают циклы for each в других языках. Это тип цикла, в котором используются итераторы. То есть каждый цикл for , который вы пишете на Python, должен использовать итератор.
Сначала давайте посмотрим на Python-эквивалент предыдущего примера, наиболее близкий к нему синтаксически:
numbers = [1,2,3,4,5] for i in range(len(numbers)): number = numbers[i] squared = number**2 print(squared)
Да, очевидно, что нужно было просто перебирать numbers , но мы хотели приблизить синтаксис к циклу for в JavaScript. Здесь нам нужно выполнить извлечение самостоятельно, поэтому мы не используем итератор по числам. Вместо этого мы создаем диапазон, который проходится по индексам чисел (итератор). Это относительно близко к циклу for на JavaScript, но этот код все равно работает на более высоком уровне абстракции.
numbers = [1,2,3,4,5] i = 0 while i < len(numbers): number = numbers[i] squared = number**2 print(squared) i += 1
Здесь мы инициализируем i , определяем, какое условие должно быть выполнено для выхода из цикла и как увеличивать i . Нам нужно очень явно управлять значением i , что не требуется при использовании диапазона.
Протокол итератора в Python
В документации Python итератор определяется как класс, реализующий __next__ и __iter__ . По этому определению итератор также является итерируемым объектом (iterable), поскольку он реализует __iter__ . Кроме того, можно сказать, что это nextable-объект, поскольку он реализует метод __next__ .
Отметим, nextable – это не часто используемый термин, потому что его можно запросто превратить в итератор. Как видите, метод __iter__ для итераторов легко реализовать. Фактически, в определении итератора явно указано, что должен делать метод:
class MyABCIterator: . def __iter__(self): return self
Вот и все, метод просто возвращает ссылку на сам итератор. Итак, если вы скопируете этот код __iter__ в nextable, вы получите итератор. Мы назвали класс MyABCIterator , поскольку мы встроим его в итератор, который выполняет итерацию по алфавиту.
Теперь давайте превратим это в итератор, сделав «некстабельным». Метод __next__ должен возвращать следующий объект в последовательности. Он также должен вызывать StopIteration при достижении конца последовательности (т.н. «исчерпание итератора»). То есть, в нашем случае — когда мы дошли до конца алфавита.
Воспользуемся удобной функцией ascii_lowercase , импортированной из модуля string стандартной библиотеки Python. Получаем строку, состоящую из строчных букв английского алфавита.
>>> from string import ascii_lowercase >>> ascii_lowercase 'abcdefghijklmnopqrstuvwxyz'
Хорошо, теперь давайте посмотрим на код нашего класса, а затем мы объясним, как он работает:
from string import ascii_lowercase class MyABCIterator: def __init__(self): self.index = 0 def __next__(self): if self.index >= len(ascii_lowercase): raise StopIteration() char = ascii_lowercase[self.index] self.index +=1 return char def __iter__(self): return self
Чтобы знать, какой символ возвращать при каждом вызове __next__ , нам понадобится индекс. Поэтому мы добавляем __init__ в наш класс, где инициализируем self.index нулем. Далее при каждом вызове __next__ мы сначала проверяем, достигли ли мы конца алфавита. Если индекс выходит за пределы строки, мы вызываем StopIteration , как указано в документации Python. Затем мы извлекаем текущий символ и увеличиваем self.index . В противном случае мы бы начали с b вместо a . Наконец, мы увеличиваем индекс и возвращаем ранее извлеченный символ.
Теперь давайте попробуем сделать это через цикл for :
>>> for char in MyABCIterator(): . print(char) a b c d e .
Мы обрезали вывод, потому что алфавит сейчас не так интересен, не правда ли? Этот итератор, как и следовало ожидать, совершенно бесполезен. Мы могли бы просто перебирать ascii_lowercase напрямую. Но, надеемся, на этом примере вы лучше разобрались в итераторах.
Последнее замечание по протоколу итераторов. Обратите внимание, как цикл for выполняет всю работу по использованию протокола. Он автоматически получает итератор, используя __iter__ , и многократно перебирает его, используя __next__ . Это соответствует всем принципам Python, когда магические методы не используются напрямую, а скорее являются способом подключиться к синтаксису Python или функциям верхнего уровня.
Nextables
Теперь, когда мы правильно реализовали наш собственный итератор, давайте немного поэкспериментируем. Первое, что нам хотелось бы узнать, это как Python относится к идее цикла по nextable ?
Для этого удалим метод __iter__ из предыдущего примера, в результате чего получим следующее:
class MyABCNextable: def __init__(self): self.index = 0 def __next__(self): if self.index >= len(ascii_lowercase): raise StopIteration() char = ascii_lowercase[self.index] self.index +=1 return char
Попытка создать цикл for с таким объектом выдает нам TypeError: 'MyABCNextable' object is not iterable . Что, знаете ли, не удивительно. Интерпретатор не может найти __iter__ для вызова и, следовательно, не может создать итератор.
Python отделяет итератор от последовательности
Мы начали экспериментировать со встроенными последовательностями и сделали небольшое забавное открытие. В Python последовательности сами по себе не являются итераторами. Скорее у каждой есть соответствующий класс-итератор, отвечающий за итерацию. Давайте посмотрим на диапазон в качестве примера:
>>> range_0_to_9 = range(10) >>> type(range_0_to_9)
range() возвращает нам объект типа range . Теперь посмотрим, что произойдет, когда мы попытаемся использовать next для такого объекта:
>>> next(range_0_to_9) Traceback (most recent call last): File "", line 1, in TypeError: 'range' object is not an iterator
Мы получили ошибку TypeError: 'range' object is not an iterator . Итак, если объект типа range не является итератором, то что мы получим при использовании iter ?
>>> range_0_to_9_iterator = iter(range_0_to_9) >>> type(range_0_to_9_iterator)
Просто для проверки используем next с range_iterator :
>>> next(range_0_to_9_iterator) 0 >>> next(range_0_to_9_iterator) 1
Здесь мы возвращаем объект range_iterator , который фактически отвечает за выполнение итерации. Видимо, это относится ко всем встроенным типам, включая списки, словари и наборы. Похоже, что в CPython разработчики решили отделить итерацию объекта от самого объекта.
Создание отдельных Iterable и Nextable
Вооружившись этими новыми знаниями об отделении итерируемого объекта от итератора, мы придумали новую идею:
Можем ли мы вернуть nextable из iterable ?
Итерация действительно проста:
class MyABCIterable: def __iter__(self): return MyABCNextable()
Это просто оболочка для нашей следующей таблицы из примера nextable. Затем пишем цикл:
for char in MyABCIterable(): print(char)
Оно работает! Итак, хотя документация Python говорит, что метод __iter__ должен возвращать итератор (реализуя как __iter__ , так и __next__ ), цикл for этого не требует.
Однако такая установка хрупкая. Возвращаясь к нашему правильно реализованному итератору, этот код будет работать:
my_abc = iter(MyABCIterator()) for char in my_abc: print(char)
А код с нашей комбинацией iterable + nextable — нет:
my_abc = iter(MyABCIterable()) for char in my_abc: print(char)
Мы получаем TypeError: 'MyABCNextable' object is not iterable . Вот поэтому протокол итератора определен так, как он определен. Это позволяет передавать итератор и по-прежнему перебирать его. Например, как если бы вы сперва создали итератор, а затем передали его в другую функцию. В таком случае наш хак не сработал бы.
Заключение
Давайте подведем итоги! Во-первых, теперь вы знаете, что все циклы for в Python используют итераторы! Кроме того, как мы увидели, итераторы в Python позволяют нам отделить код, выполняющий итерацию, от кода, работающего с каждым элементом. Мы также надеемся, что вы узнали немного больше о том, как работают итераторы в Python и что такое протокол итератора.
Последний вывод, к которому мы пришли, немного более абстрактный и не связан конкретно с итераторами или даже с Python. Может показаться, что код работает хорошо, пока вы не наткнетесь на случай, когда он сломается. Просто попробовав комбинацию iterable + nextable в цикле for , мы бы не обнаружили, что код ломается при передаче итератора.
На этом пока все, и мы надеемся, вам понравился более глубокий взгляд на протокол итераторов в Python!
Успехов в написании кода!
