views:

273

answers:

3

I have a generator and I would like to know if I can use it without having to worry about StopIteration , and I would like to use it without the for item in generator . I would like to use it with a while statement for example ( or other constructs ). How could I do that ?

+13  A: 

built-in function

next(iterator[, default])
Retrieve the next item from the iterator by calling its __next__() method. If default is given, it is returned if the iterator is exhausted, otherwise StopIteration is raised.

In Python 2.5 and older:

raiseStopIteration = object()
def next(iterator, default=raiseStopIteration):
    if not hasattr(iterator, 'next'):
       raise TypeError("not an iterator")
    try:
       return iterator.next()
    except StopIteration:
        if default is raiseStopIteration:
           raise
        else:
           return default
SilentGhost
I've added `next()` implementation for Python 2.5
J.F. Sebastian
This is a better solution than the accepted one.
Carl Meyer
A: 

Use this to wrap your generator:

class GeneratorWrap(object):

      def __init__(self, generator):
          self.generator = generator

      def __iter__(self):
          return self

      def next(self):
          for o in self.generator:
              return o
          raise StopIteration # If you don't care about the iterator protocol, remove this line and the __iter__ method.

Use it like this:

def example_generator():
    for i in [1,2,3,4,5]:
        yield i

gen = GeneratorWrap(example_generator())
print gen.next()  # prints 1
print gen.next()  # prints 2

Update: Please use the answer below because it is much better than this one.

Evan Fosmark
what happens when I call it the sixth time ?
Geo
@Tsunami, it returns None.
Evan Fosmark
@Tsunami, I should also mention that this behavior can be changed by adding another return statement below the for loop in the next() method. Whatever you have it return will be the default after the generator has exhausted.
Evan Fosmark
By the way,can you please explain what's the logic behind returning self in __iter__ ?
Geo
An iterator `next()` method must raise StopIteration. Your `__iter__()` method returns `self` but the object breaks the contract for iterator object.
J.F. Sebastian
@Tsunami, no problem. Basically, it isn't too useful in this example, but it allows for it to be used in a "for x in y" sort of loop if it were needed to. You could remove it if you are 100% sure that you'll never use it that way.
Evan Fosmark
@J.F, nice catch. Updated.
Evan Fosmark
I understand that not raising the exception breaks the contract , but adding the line brings us back to square 1 :) . Anyway , thanks for your response and your helpful comments !
Geo
If you don't want an iterator, don't use it. In this case don't use `next` name for the method if your object is not an iterator. Call it something else e.g. `safe_next(self, sentinel=None)` method -- return `sentinel` instead of throwing StopIteration. builtin `next()` function is different.
J.F. Sebastian
A better name would be `next_noraise()` for the method. Even better just use @SilentGhost's suggestion.
J.F. Sebastian
+1  A: 

Another options is to read all generator values at once:

>>> alist = list(agenerator)

Example:

>>> def f():
...   yield 'a'
...
>>> a = list(f())
>>> a[0]
'a'
>>> len(a)
1
J.F. Sebastian
this defeats the point!!!
hasen j
@hasen: list[index] doesn't raise StopIteration. It can be used without `for` loop. It can be used with a `while` loop. All conditions from the question are satisfied.
J.F. Sebastian
Though @SilentGhost's approach is better in my opinion.
J.F. Sebastian
Try reading all generator values of itertools.count() :)
Torsten Marek
As soon as @Torsten Marek get StopIteration from itertools.count() I'll read all values. :) OP asks only about generators that can produce StopIteration, so infinite generators just do not apply.
J.F. Sebastian