Munna
Munna

Reputation: 336

Difference between <type 'generator'> and <type 'xrange'>

I have seen in a lot of posts/materials saying xrange(num) is a generator/iterator. I have a couple of questions regarding that.

  1. I want to know the exact difference between type 'xrange' and type 'generator'
  2. 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

Answers (3)

C S
C S

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

juanpa.arrivillaga
juanpa.arrivillaga

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

user94559
user94559

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

Related Questions