Якщо range () є генератором у Python 3.3, чому я не можу викликати next () для діапазону?


84

Можливо, я став жертвою дезінформації в Інтернеті, але, думаю, швидше за все, я щось неправильно зрозумів. Виходячи з того, що я дізнався до цього часу, 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. Генератори - це категорія ітераторів (функції генератора та вирази генератора). Принаймні я так думаю ...
Олег Припін

Відповіді:


109

rangeє класом незмінних ітерабельних об'єктів. Їх ітераційну поведінку можна порівняти з lists: ви не можете телефонувати nextбезпосередньо до них; вам потрібно отримати ітератор за допомогою iter.

Так що ні, rangeце не генератор.

Ви можете подумати: "чому вони не зробили це безпосередньо ітерабельним"? Ну, rangeу нас є кілька корисних властивостей, які не були б можливими таким чином:

  • Вони незмінні, тому їх можна використовувати як словникові ключі.
  • У них є 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

11
Ще однією приємною особливістю rangeоб'єктів є те, що вони мають __contains__метод, за допомогою якого можна перевірити, чи знаходиться значення в діапазоні:5 in range(10) => True
kindall

Дякую за відповідь; це має сенс зараз. Єдине, що я хочу роз’яснити, перш ніж прийняти вашу відповідь, - це примітка курсивом приблизно на третині шляху до цієї сторінки, де сказано, що «в Python 3 range () є генератором». Це просто неправильно?
Джефф,

3
@Jeff Власне кажучи, так, це неправильно. Автор записки , ймовірно , мав в виду , що в Python 3 rangeє лінивим ( по порівнянні з Python 2 , де це просто функція , яка повертає список).
Олег Припін

6
Також: range(0,10,3)[3]і 9 in range(0,10,3). Діапазон - це майже лінивий список.
Леннарт Регебро

2
@ user3079275 "безпосередньо ітерабельний" - це неправильна назва, насправді означає "ітератор". Ітератори мають внутрішній стан, тому вони можуть змінюватися за визначенням. "Ітерабельний" - це об'єкт, незалежно від того, змінюється він чи ні, який може створити ітератор. Навіть змінні об'єкти, як правило, самі по собі не є ітераторами, натомість вони створюють ітератори багаторазово (наприклад, ви можете перебирати один і той же список у двох різних місцях самостійно, використовуючи два ітератори).
Олег Припін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.