Jeśli range () jest generatorem w Pythonie 3.3, dlaczego nie mogę wywołać metody next () w zakresie?

87

Być może padłem ofiarą dezinformacji w sieci, ale wydaje mi się, że jest bardziej prawdopodobne, że coś źle zrozumiałem. Opierając się na tym, czego się do tej pory nauczyłem, range () jest generatorem, a generatory mogą być używane jako iteratory. Jednak ten kod:

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

daje mi ten błąd:

TypeError: 'range' object is not an iterator

Czego tu brakuje? Spodziewałem się, że to wypisze 0 i przejdzie do następnej wartości w myrange. Jestem nowy w Pythonie, więc proszę przyjąć moje przeprosiny za dość podstawowe pytanie, ale nigdzie indziej nie mogłem znaleźć dobrego wyjaśnienia.

Jeff
źródło
2
Zobacz stackoverflow.com/q/13054057/395760, aby zobaczyć rozróżnienie między iteratorami a rzeczami, które możesz iterować w forpętli.
1
Czy poprawne byłoby stwierdzenie, że generatory są iterowalne, ale nie są iteratorami?
Jeff
4
@Jeff Iterables to obiekty, iterktórych można użyć do uzyskania iteratora. Iteratory to obiekty, które można iterować przy użyciu next. Generatory to kategoria iteratorów (funkcje generatora i wyrażenia generatora). Przynajmniej tak mi się wydaje ...
Oleh Prypin

Odpowiedzi:

115

rangejest klasą niezmiennych obiektów iterowalnych. Ich zachowanie w iteracji można porównać do lists: nie można ich nextbezpośrednio wywoływać ; musisz uzyskać iterator za pomocą iter.

Więc nie, rangenie jest generatorem.

Możesz pomyśleć: „dlaczego nie uczyniły tego bezpośrednio iterowalnymi”? Cóż, rangemają kilka przydatnych właściwości, które nie byłyby możliwe w ten sposób:

  • Są niezmienne, więc mogą być używane jako klucze słownikowe.
  • Mają atrybuty start, stopi step(od Pythona 3.3) countoraz indexmetody i obsługują in, leni __getitem__operacje.
  • Możesz rangewielokrotnie powtarzać to samo .

>>> 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
Oleh Prypin
źródło
12
Kolejną fajną cechą rangeobiektów jest to, że mają one __contains__metodę, której można użyć do sprawdzenia, czy wartość mieści się w zakresie:5 in range(10) => True
kindall
Dziękuję za odpowiedź; ma to teraz sens. Jedyną rzeczą, którą chcę wyjaśnić przed zaakceptowaniem odpowiedź jest uwaga kursywą około jednej trzeciej drodze w dół tej strony, która stwierdza, że „w Pythonie 3 Zakres () jest generatorem”. Czy to po prostu niepoprawne?
Jeff
4
@Jeff Ściśle mówiąc, tak, to jest złe. Autor notatki zapewne miał na myśli, że w Pythonie 3 rangejest leniwy (w porównaniu do Pythona 2, gdzie jest to po prostu funkcja zwracająca listę).
Oleh Prypin
6
Również: range(0,10,3)[3]i 9 in range(0,10,3). Zakres to raczej leniwa lista.
Lennart Regebro
2
@ user3079275 „bezpośrednio iterowalny” to błędne określenie, które w rzeczywistości oznacza „iterator”. Iteratory mają stan wewnętrzny, więc z definicji są zmienne. „Iterowalny” to obiekt, niezależnie od tego, czy jest zmienny, czy nie, który może utworzyć iterator. Nawet zmienne obiekty nie są zwykle same iteratorami, zamiast tego wytwarzają iteratory w sposób wielokrotnego użytku (na przykład można iterować po tej samej liście w dwóch różnych miejscach niezależnie, używając dwóch iteratorów).
Oleh Prypin