Если range () является генератором в Python 3.3, почему я не могу вызвать next () для диапазона?

86

Возможно, я стал жертвой дезинформации в сети, но я думаю, что более вероятно, что я просто что-то неправильно понял. На основании того, что я узнал до сих пор, range () является генератором, а генераторы могут использоваться как итераторы. Однако этот код:

myrange = range(10)
print(next(myrange))

дает мне эту ошибку:

TypeError: 'range' object is not an iterator

Что мне здесь не хватает? Я ожидал, что это напечатает 0 и перейдет к следующему значению в myrange. Я новичок в Python, поэтому примите мои извинения за довольно простой вопрос, но я нигде не нашел хорошего объяснения.

Джефф
источник
2
См. Stackoverflow.com/q/13054057/395760, чтобы узнать о различиях между итераторами и вещами, которые можно перебирать в forцикле.
1
Было бы правильно сказать, что итераторами являются генераторы, но не итераторы?
Джефф
4
@Jeff Iterables - это объекты, которые iterможно использовать для получения итератора. Итераторы - это объекты, которые можно повторять с помощью next. Генераторы - это категория итераторов (функций генератора и выражений генератора). По крайней мере, я так думаю ...
Олег Припин

Ответы:

115

rangeэто класс неизменяемых итеративных объектов. Их итерационное поведение можно сравнить с lists: вы не можете обращаться к ним nextнапрямую; вам нужно получить итератор, используя iter.

Так что нет, rangeэто не генератор.

Вы можете подумать: «Почему они не сделали это напрямую итеративным»? Что ж, у ranges есть несколько полезных свойств, которые были бы невозможны таким образом:

  • Они неизменяемы, поэтому их можно использовать как ключи словаря.
  • У них есть атрибуты start, stopи step(начиная с Python 3.3), countи indexметоды, которые они поддерживают in, lenи __getitem__операции.
  • Вы можете повторять одно и то же rangeнесколько раз.

>>> myrange = range(1, 21, 2)
>>> myrange.start
1
>>> myrange.step
2
>>> myrange.index(17)
8
>>> myrange.index(18)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: 18 is not in range
>>> it = iter(myrange)
>>> it
<range_iterator object at 0x7f504a9be960>
>>> next(it)
1
>>> next(it)
3
>>> next(it)
5
Олег Припин
источник
12
Еще одна приятная особенность rangeобъектов заключается в том, что у них есть __contains__метод, который можно использовать для проверки, находится ли значение в диапазоне:5 in range(10) => True
kindall
Спасибо за ответ; теперь это имеет смысл. Единственное, что я хочу прояснить, прежде чем принять ваш ответ, - это примечание, выделенное курсивом примерно на трети расстояния вниз по этой странице, в котором говорится, что «в Python 3 range () является генератором». Это просто неправильно?
Джефф
4
@Jeff Строго говоря, да, это неправильно. Автор записки , вероятно , имел в виду , что в Python 3 rangeявляется ленивым ( по сравнению с Python 2 , где это просто функция , которая возвращает список).
Олег Припин
6
Также: range(0,10,3)[3]и 9 in range(0,10,3). Range - это довольно ленивый список.
Леннарт Регебро
2
@ user3079275 «итерация напрямую» - неправильное название, фактически означающее «итератор». Итераторы имеют внутреннее состояние и поэтому по определению являются изменяемыми. Iterable - это объект, изменяемый он или нет, который может создавать итератор. Даже изменяемые объекты обычно не являются итераторами сами по себе, вместо этого они создают итераторы с возможностью повторного использования (например, вы можете перебирать один и тот же список в двух разных местах независимо, используя два итератора).
Олег Припин