[Tutor] 2.7.3 generator objects

Peter Otten __peter__ at web.de
Sun Sep 2 13:31:31 CEST 2012


Steven D'Aprano wrote:

> On 02/09/12 17:09, Ray Jones wrote:
> 
>> But didn't I read somewhere that you can reset an iterator to go through
>> the whole process again?
> 
> In general, no.
> 
> The usual way to "reset" an iterator is to re-create it.
> 
> 
> walker = os.walk("/home/steve/start")
> # ... process files in walker
> walker = os.walk("/home/steve/start")
> # ... and process them again
 
 
Python doesn't enforce this behaviour for iterators in general, but it is 
part of the spec:

""" 
    - Once a particular iterator object has raised StopIteration, will
      it also raise StopIteration on all subsequent next() calls?
      Some say that it would be useful to require this, others say
      that it is useful to leave this open to individual iterators.
      Note that this may require an additional state bit for some
      iterator implementations (e.g. function-wrapping iterators).

      Resolution: once StopIteration is raised, calling it.next()
      continues to raise StopIteration.
"""

See http://www.python.org/dev/peps/pep-0234/

For illustration purposes here's a non-compliant iterator:

WRONG:

>>> class Iterator:
...     def __init__(self, max, factor=2):
...             self.max = max
...             self.factor = factor
...             self.value = 1
...     def __iter__(self):
...             return self
...     def __next__(self):
...             result = self.value
...             if result >= self.max:
...                     raise StopIteration
...             self.value *= self.factor
...             return result
...     def reset(self):
...             self.value = 1
... 
>>> it = Iterator(8)
>>> next(it)
1
>>> next(it)
2
>>> next(it)
4
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in __next__
StopIteration
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in __next__
StopIteration
>>> it.reset()
>>> next(it)
1
>>> next(it)
2

BETTER (Iterator is the same as above, without* the reset() method):

>>> class Iterable:
...     def __init__(self, max, factor=2):
...             self.max = max
...             self.factor = factor
...     def __iter__(self):
...             return Iterator(self.max, self.factor)
... 
>>> it = Iterable(8)

>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Iterable' object is not an iterator
>>> x = iter(it)
>>> next(x)
1
>>> next(x)
2
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in __next__
StopIteration

Now instead of resetting the iterator ask the iterable for a new iterator:

>>> x = iter(it)
>>> next(x)
1
>>> next(x)
2
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in __next__
StopIteration

(*) Of course I cheated and left it in ;)

PS: Since the advent of generators people usually write

def g(max, factor=2):
    value = 1
    while value < max:
        yield value
        value *= factor

They're all lazy bastards...

PPS: 'max' should rather be called 'stop' since it indicates the upper bound 
of a half-open interval.




More information about the Tutor mailing list