Why is the iterable object not an iterator?

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) 

Mistake:

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

I do not know why the linehistory object linehistory not an iterator, since it already included the __iter__ method in the the class.

+7
python iterator
source share
4 answers

In short, iterable is the object that I want to iterate over. It has __iter__() .

However, an β€œiterator” is an object that is used for iteration. It has next() or __next__() . Since any iterator is also iterated (being its own iterator), it has __iter__() .

You can get an iterator for any iteration using iter(obj) .

In your example, linehistory (which linehistory should be written) is iterated, since it has .__iter__() . The generated generated object is an iterator (like every generator object).

+7
source share

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

Wrong. See Types of Iterators :

Iterators themselves must support the following two methods, which together form the iterator protocol:

iterator.__iter__()
Return the iterator object itself. This is necessary for using both containers and iterators with for and in instructions. This method matches the tp_iter word 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 additional elements, call a StopIteration exception. This method matches the tp_iternext word of the type structure for Python objects in the Python / C API.

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

Pythons generators provide a convenient way to implement the iterator protocol. If the __iter__() container object is implemented as a generator, it automatically returns an iterator object (technically, a generator object) that provides the __iter__() and __next__() methods. More information on generators can be found in the documentation for the yield statement.

+4
source share

Iterator objects need the __iter__ method, but they must also have implemented next :

Iterators themselves must 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 function names:

iterator .__ iter __ ()

iterator .__ next __ ()

Python 3.x Source

+3
source share

As pointed out in the comments and another answer, you also need to define the next() method in your class to make it an iterator.

In addition, I notice that you are using yield instead of return . Because of which, executing next(lines) will not print the next line, but it will give you a generator object.

 class linehistory: def __init__(self, lines, histlen=3): self.lines = lines self.history = deque(maxlen=histlen) def __iter__(self): return self def clear(self): self.history.clear() def next(self): # Python 3: __next__(self) for lineno, line in enumerate(self.lines,1): self.history.append((lineno, line)) return line else: raise StopIteration 

And then do the following:

 >>> f = open("somefile.txt") >>> lines = linehistory(f) >>> next(lines) ... "Some line" 

If you use yield instead of return , it will give a generator object:

 >>> = open("somefile.txt") >>> lines = linehistory(f) >>> next(lines) ... <generator object next at 0xb4bb961c> >>> # To print the line you'll have to do: >>> next(next(lines)) ... "Some line" 
0
source share

All Articles