Reputation: 336
I have seen in a lot of posts/materials saying xrange(num) is a generator/iterator. I have a couple of questions regarding that.
If xrange is an iterator/generator, it is supposed to have .next() method. I do not understand why the .next() method doesn't work for the case below.
def generator():
for i in xrange(20): yield i
In the above example,
numbers = generator()
for i in numbers:
if i == 6: break
for i in numbers:
if i == 10: break
print i
>>> 7
8
9
>>> print numbers.next()
11
The above functionalities also hold true for a object generator of the type:
>>> numbers = (x for x in range(100))
If I do with xrange operation, the loop starts iterating from the beginning and there is no next() operation. I know that we can do the smart way of:
for i in xrange(20):
if (#something):
var = i
break
#perform some operations
for i in range(var,20):
#Do something
But I want to loop to continue after var without using var.
To be short, is there a next() kind of operation for xrange. If yes : 'How?' , else : 'Why?'
Upvotes: 6
Views: 2132
Reputation: 1525
First you should note an xrange object is not a generator:
>>> xrange_obj = xrange(10000)
>>> type(xrange_obj)
xrange
>>> gen_obj = (x for x in range(10000))
>>> type(gen_obj)
generator
>>> import types
>>> isinstance(xrange_obj, types.GeneratorType)
False
>>> isinstance(gen_obj, types.GeneratorType)
True
It is an iterable (but not an iterator)
>>> iter(xrange_obj)
<rangeiterator at 0x3e07f930>
>>> iter(xrange_obj).next()
0
Finally, as I don't see it touched upon in the other answers currently, the reason xrange is not a generator but its own type of object is because it supports special methods to make it mimic a range
.
>>> xrange_obj[1]
1
>>> len(xrange_obj)
10000
>>> gen_obj[1]
TypeError: 'generator' object has no attribute '__getitem__'
>>> len(gen_obj)
TypeError: object of type 'generator' has no len()
Upvotes: 3
Reputation: 95948
Also, you should understand that an iterator and an generator are not the same thing. An iterable is any Python object that implements an __iter__
method that returns an iterator. An iterator also must implement an __iter__
method but also a next
method (__next__
in Python 3). So xrange
is iterable, but not an iterator. Here is an iterator:
class NumberCounter(object):
def __init__(self, size):
self.size = size
self.start = 0
def __iter__(self):
return self
def next(self):
if self.start < self.size:
self.start += 1
return self.start
raise StopIteration
In the interactive interpreter:
>>> nc6 = NumberCounter(6)
>>> it = iter(nc6)
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
4
>>> next(it)
5
>>> next(it)
6
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in next
StopIteration
>>> for i in NumberCounter(6):
... print(i)
...
1
2
3
4
5
6
>>>
A generator is a Python construct that helps you create iterators easily.
From the docs:
Generators are a simple and powerful tool for creating iterators. They are written like regular functions but use the yield statement whenever they want to return data. Each time next() is called on it, the generator resumes where it left off (it remembers all the data values and which statement was last executed)... Anything that can be done with generators can also be done with class-based iterators as described in the previous section. What makes generators so compact is that the __iter__() and next() methods are created automatically...In addition to automatic method creation and saving program state, when generators terminate, they automatically raise StopIteration. In combination, these features make it easy to create iterators with no more effort than writing a regular function.
Here is a generator:
def number_counter(x):
curr = 1
while curr <= x:
yield curr
curr += 1
In the interactive interpreter:
>>> for i in number_counter(6):
... print(i)
...
1
2
3
4
5
6
>>>
Here's another:
def wacky_gen():
yield 88
yield 2
yield 15
Finally...
>>> for i in wacky_gen():
... print(i)
...
88
2
15
>>>
Upvotes: 2
Reputation: 60143
xrange
is an iterable, so you can call iter
to get an iterator out of it.
>>> x = xrange(20)
>>> iterator = iter(x)
>>> for i in iterator:
... if i == 6: break
...
>>> iterator.next()
7
Upvotes: 5