2026, Jan 01 09:02

Почему next() выдаёт TypeError, а цикл for работает: итераторы и итерируемые в Python (пример spaCy Doc)

Разбираем разницу между итерируемыми и итераторами в Python на примере spaCy Doc: почему next() даёт TypeError, а for работает. Показаны iter() и примеры.

Иногда Python на первый взгляд кажется непоследовательным: вы вызываете next() для объекта и получаете TypeError, а цикл for по тому же объекту отрабатывает безупречно. Это классический случай путаницы между итератором и итерируемым объектом. Разберём конкретный пример, поймём, почему так происходит, и как писать код, который делает именно то, что вы ожидаете.

Воспроизводим проблему

Ниже показано поведение в интерактивной сессии. Вызов next() для spaCy Doc заканчивается ошибкой, а итерация в цикле for печатает токены, как и ожидается.

>>> import spacy
>>> pipe = spacy.load("en_core_web_sm")
>>> text_doc = pipe("Berlin looks like a nice city")
>>> next(text_doc)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'spacy.tokens.doc.Doc' object is not an iterator
>>> for tok in text_doc:
...     print(tok)
...
Berlin
looks
like
a
nice
city

Что происходит на самом деле

Ключ к пониманию — различие между итерируемым объектом и итератором. Итерируемый объект — это то, по чему можно проходиться в цикле. Итератор — объект, который выдаёт элементы по одному для такого цикла. Многие повседневные объекты Python являются итерируемыми, но при этом не являются итераторами: списки, строки, кортежи и range — все они подходят под это описание, как и spaCy Doc в примере выше.

Работая с итерируемыми объектами, обычно нет необходимости вызывать iter() или вручную управлять итераторами. Оператор for делает это автоматически, создавая временную безымянную переменную для хранения итератора на время выполнения цикла.

Отсюда и асимметрия. next(text_doc) вызывает TypeError, потому что text_doc — не итератор. Цикл for работает, потому что Python неявно вызывает iter(text_doc), получает итератор и продвигает его до исчерпания.

Как исправить и сформулировать явно

Если вам нужно просто пройтись по объекту, используйте for — Python сам позаботится об итераторе. Если же требуется вручную извлекать элементы по одному, сначала получите итератор из итерируемого объекта с помощью iter(), а затем вызывайте next() уже на этом итераторе.

import spacy

runner = spacy.load("en_core_web_sm")
doc_obj = runner("Berlin looks like a nice city")

# Вариант 1: идиоматичный обход (Python сам вызывает iter() за вас)
for item in doc_obj:
    print(item)

# Вариант 2: ручная итерация, когда нужен явный контроль
walker = iter(doc_obj)
print(next(walker))  # Berlin
print(next(walker))  # looks
# ... продолжайте по необходимости

Почему это важно

Понимание различий между итерируемым объектом и итератором экономит время при отладке и чтении кода. Становится ясно, почему next() иногда падает, а конструкция for ... in ... работает для широкого круга объектов. С корректной ментальной моделью проще выбрать инструмент: обычный цикл для большинства задач или явное управление через iter() и next(), когда нужно поэтапно потреблять элементы.

Выводы

Не каждый объект, по которому можно итерироваться, является итератором. Если next() выбрасывает TypeError, скорее всего, перед вами итерируемый объект. Позвольте циклу for сделать работу за вас или, если нужен ручной шаг, сначала вызовите iter(), чтобы получить итератор, а затем применяйте next() уже к нему. Помня об этом различии, вы избавитесь от типичной путаницы при итерациях в Python.