Why is an iterable object not an iterator?

2020-08-09 06:44发布

Here is my code:

from collections import deque

class linehistory:
    def __init__(self, lines, histlen=3):
        self.lines = lines
        self.history = deque(maxlen=histlen)

    def __iter__(self):
        for lineno, line in enumerate(self.lines,1):
            self.history.append((lineno, line))
            yield line

    def clear(self):
        self.history.clear()


f = open('somefile.txt')
lines = linehistory(f)
next(lines)

Error:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
    TypeError: 'linehistory' object is not an iterator

I have no idea why the linehistory object is not an iterator since it has already included __iter__ method in the class.

4条回答
等我变得足够好
2楼-- · 2020-08-09 06:54

I have no idea why the linehistory object is not an iterator since it has already included __iter__ method in the class.

Wrong. See Iterator Types:

The iterator objects themselves are required to support the following two methods, which together form the iterator protocol:

iterator.__iter__()
Return the iterator object itself. This is required to allow both containers and iterators to be used with the for and in statements. This method corresponds to the tp_iter slot of the type structure for Python objects in the Python/C API.

iterator.__next__()
Return the next item from the container. If there are no further items, raise the StopIteration exception. This method corresponds to the tp_iternext slot of the type structure for Python objects in the Python/C API.

However you can iterate over lines, that's because your __iter__ method is a generator function, see Generator Types:

Python’s generators provide a convenient way to implement the iterator protocol. If a container object’s __iter__() method is implemented as a generator, it will automatically return an iterator object (technically, a generator object) supplying the __iter__() and __next__() methods. More information about generators can be found in the documentation for the yield expression.

查看更多
Viruses.
3楼-- · 2020-08-09 06:57

Actually,

All these other answers are wrong (except for @glglgl who has an obtuse style of writing). Your generator function __iter__() would work as is if you called it with a for loop like so

for line in lines:
    print(line)

But because you used next(lines) you have to first use iter() to get the iterator (I presume it just calls __iter__() on the object) like so

it = iter(lines)
print(next(it))

as Mr.Beazley points out

查看更多
Anthone
4楼-- · 2020-08-09 07:13

The concept of iteration is well documented in the Python documentation.

In short, "iterable" is the object I want to iterate over, also called the container. This can be a list, a string, a tuple or anything else which consists of or can produce several items. It has __iter__() which returns an iterator.

An "iterator" is the object which is used for one iteration. It can be seen as a kind of "cursor". It has next() (in Python 2) or __next__() (in Python 3) which is called repeatedly until it raises a StopIteration exception. As any iterator is iterable as well (being its own iterator), it also has __iter__() which returns itself.

You can get an iterator for any iterable with iter(obj).

In your example, linehistory (which should be written LineHistory) is iterable as it has an .__iter__(). The generator object created with this is an iterator (as every generator object).

查看更多
Animai°情兽
5楼-- · 2020-08-09 07:21

Iterator objects need an __iter__ method but they also need to have next implemented:

The iterator objects themselves are required to support the following two methods, which together form the iterator protocol:

iterator.__iter__()
Return the iterator object itself.

iterator.next()
Return the next item from the container.

Python 2.7 Source

In Python 3.x these are the function names:

iterator.__iter__()

iterator.__next__()

Python 3.x Source

查看更多
登录 后发表回答