Оцените назначение __length_hint__ в этом итераторе: может ли его значение изменить элементы результата list()?
class Cursor:
def __init__(self, items):
self.items = items
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index == len(self.items):
raise StopIteration
value = self.items[self.index]
self.index += 1
return value
def __length_hint__(self):
return 10_000
result = list(Cursor([1, 2, 3]))
__length_hint__ сообщает контейнеру приблизительное число оставшихся элементов для предварительного резервирования памяти. В приведённом примере итоговый список всё равно содержит только [1, 2, 3]: подсказка не определяет состав результата и не заменяет корректную работу __next__.
Если реализация Python использует эту подсказку, завышенное значение может привести лишь к лишнему резервированию памяти или дополнительным накладным расходам. Метод необязателен, поэтому код не должен рассчитывать на его вызов.
Итераторы проектировались как ленивый способ последовательной выдачи элементов без обязательного хранения всей коллекции в памяти. Такой подход удобен для файлов, потоков и больших вычислений, но заранее не сообщает контейнерам точный размер результата.
При материализации итератора, например через list(), реализация может захотеть заранее зарезервировать память. Для этого существует необязательный механизм length hint — оценка оставшегося количества элементов без изменения самого протокола итерации.
list() должен получить элементы, вызывая next(), но список может расти по мере добавления значений. Частые перераспределения памяти ухудшают производительность, особенно при больших объёмах данных.
При этом итератор может быть вычислительно сложным, бесконечным или изменять число оставшихся элементов во время обхода. Поэтому подсказка не может быть обязательным точным размером: она предназначена только для оптимизации, а не для управления логикой обхода.
Специальный метод __length_hint__() может вернуть неотрицательную оценку числа элементов, которые итератор ещё способен выдать. Реализация контейнера вправе использовать эту оценку для предварительного выделения памяти, но вправе её проигнорировать.
В коде __length_hint__() возвращает 10_000, хотя итератор выдаёт три значения. Состав результата определяется исключительно последовательностью вызовов __next__(): после значений 1, 2 и 3 будет поднят StopIteration.
В CPython подобная подсказка может использоваться при создании списка или выполнении операций из operator, но полагаться на конкретное количество выделенной памяти или на факт вызова метода нельзя. Если метод отсутствует, итератор остаётся корректным; контейнер просто использует другой способ роста.
Подсказка должна быть дешёвой и не иметь побочных эффектов. Нельзя использовать её для чтения из потока, продвижения итератора или вычисления результата: такие действия нарушат ожидаемую семантику и могут привести к пропуску элементов.
Сервис формирует список идентификаторов из большого курсора базы данных. Разработчик добавляет __length_hint__, возвращающий количество строк, известное из COUNT(*), чтобы уменьшить число перераспределений памяти.
Вариант без подсказки прост и надёжен, но при материализации очень большого результата может потребовать больше операций расширения списка. Вариант с точной оценкой может ускорить построение, однако отдельный COUNT(*) создаёт дополнительный запрос и может устареть до завершения чтения.
Оптимальным решением будет использовать подсказку только тогда, когда оценка уже доступна практически бесплатно и её вычисление не меняет состояние курсора. Если размер неизвестен или поток динамический, лучше не реализовывать метод: корректность обхода важнее потенциальной оптимизации. В результате list() получает те же элементы, а выигрыш возможен только на уровне выделения памяти.
__length_hint__() возвращать точное число оставшихся элементов?Нет. Это оценка, а не часть логики итерации. Она может быть завышенной или заниженной, но должна соответствовать требованиям протокола: обычно возвращается неотрицательное целое значение. Точный состав и количество результата по-прежнему определяются __next__().
__length_hint__()?Итерация не станет некорректной. Потребитель, которому нужна оценка, может использовать другие доступные сведения, например __len__(), либо выбрать консервативную стратегию расширения контейнера. Наличие метода — необязательная оптимизация, а не требование протокола итератора.
__length_hint__() через вызов next()?Потому что next() изменяет состояние итератора и извлекает элемент. Если подсчёт оценки продвинет итератор, последующая материализация потеряет эти элементы; кроме того, вычисление подсказки может неожиданно выполнить дорогую операцию или вызвать побочные эффекты. __length_hint__() должен только сообщать оценку и не вмешиваться в последовательность обхода.