osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

2 Bugs: in Python 3 tutorial, and in bugs.python.org tracker registration system


Francis Esmonde-White wrote:

> Hello,
> 
> I came to report one bug (in the Python 3 tutorial documentation), and ran
> into another (functional in bugs.python.org bug tracker registration
> system). I have included details for both bugs below.
> 
> Thanks in advance for your assistance, I am loving Python and the
> documentation!
> 
> Warm regards,
> Francis Esmonde-White
> 
> ###############################
> 
> *Bug 1:*
> In the Python 3 tutorial section 9.8 for iterators
> <https://docs.python.org/3/tutorial/classes.html#iterators>, the example
> code for the iterator class Reverse has the iterator self.index defined
> only in the __init__. This approach will only allow the iterator to run
> once. Would it be better to show an example with the index defined as both
> a private field (self._index) and with the index value being populated in
> the __index__() function so that the method can be run more than once?
> 
> I think that the code example should look like the following:
> 
> class Reverse:
>     """Iterator for looping over a sequence backwards."""
>     def __init__(self, data):
>         self.data = data
> 
>     def __iter__(self):
>         # Define the setup for the iteration here,
>         # so that the index is always right when the iteration starts.
>         self._index = len(self.data)
>         return self
> 
>     def __next__(self):
>         if self._index == 0:
>             raise StopIteration
>         self._index = self._index - 1
>         return self.data[self._index]

No, "iterators" are meant to be one-off, and their __iter__ method should 
just return self, while "iterables" give you a new iterater on each 
invocation of __iter__.

You can easily build an iterable on top of the Reverse class in the tutorial 
(see ReverseIterable below). You could also turn Reverse into an iterable, 
but your implementation is something in between with confusing behaviour. 
Compare:

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

class ReverseFrancis:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data

    def __iter__(self):
        # Define the setup for the iteration here,
        # so that the index is always right when the iteration starts.
        self._index = len(self.data)
        return self

    def __next__(self):
        if self._index == 0:
            raise StopIteration
        self._index = self._index - 1
        return self.data[self._index]

class ReverseIterable:
    def __init__(self, data):
        self.data = data
    def __iter__(self):
        return Reverse(self.data)

data = range(5)
for class_ in Reverse, ReverseIterable, ReverseFrancis:
    print(class_.__name__)
    it = class_(data)
    for pair in zip(it, it):
        iter(it)
        print(*pair)

$ python3 tmp.py | head
Reverse
4 3
2 1
ReverseIterable
4 4
3 3
2 2
1 1
0 0
ReverseFrancis
4 3
4 3
4 3
4 3
4 3
4 3
4 3
4 3
4 3
4 3
Traceback (most recent call last):
  File "tmp.py", line 45, in <module>
    print(*pair)
BrokenPipeError: [Errno 32] Broken pipe
$