[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