27

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.

martineau
  • 112,593
  • 23
  • 157
  • 280
pipi
  • 645
  • 1
  • 8
  • 15
  • 2
    You also need to define a `next()` method (or `__next__()` for Python 3). – xyres Nov 27 '15 at 11:13
  • `__next__` method missing: http://pymbook.readthedocs.org/en/latest/igd.html#iterators – user2390182 Nov 27 '15 at 11:15
  • 10
    An `__iter__` method makes your object an [iterable](https://docs.python.org/3.5/library/collections.abc.html#collections.abc.Iterable), while a `__next__` method makes it an [iterator](https://docs.python.org/3.5/library/collections.abc.html#collections.abc.Iterator). Use `lines = iter(linehistory(f))` and you'll be fine. – Vincent Nov 27 '15 at 11:17
  • 1
    See also: http://stackoverflow.com/q/9884132/296974 – glglgl Nov 27 '15 at 15:41

5 Answers5

27

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).

glglgl
  • 85,390
  • 12
  • 140
  • 213
20

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

CpILL
  • 5,258
  • 3
  • 35
  • 33
  • 3
    "glglgl who has an obtuse style of writing" Could you elaborate on that? – glglgl May 06 '20 at 06:11
  • @glglgl You say the same thing twice and nether time does it bring clarity i.e. `"iterable" is the object I want to iterate over` and again `"iterator" is the object which is used for iteration`. You introduce `next` with explanation of what that is and do it again with generators. You basically assume the reader knows what: iteration, next(), and generators. But mainly you don't provide examples nor references to the introduced concepts. – CpILL May 08 '20 at 04:08
  • 1
    Thank you for your comment. I added some more clarity (hopefully) and provided a link to help the reader find the "original" definition. – glglgl May 08 '20 at 07:42
  • In my case (from your first example), `line` is a dict object. How can I extract just the keys from this (without looping through everything)? – LShaver Jun 05 '20 at 20:11
  • @LShaver if you use a for loop on a `dict` you get just the keys. You can also use the `.keys()` on a `dict` and get a keys iterable which works like `lines` in the above example – CpILL Jun 07 '20 at 20:55
4

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.

laike9m
  • 16,616
  • 19
  • 96
  • 130
1

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

SuperBiasedMan
  • 9,484
  • 10
  • 45
  • 70
0

Your object it is not an iterator the same way a list it is not an iterator, but an iterable. You could make it an iterator, though. Because an iterator is an object in its own.

help(list)

Then:

 |  __iter__(self, /)
 |      Implement iter(self).

Suppose you have a list:

a = [1,2,3]

and you try to call

next(a)

You get:

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

But you can make an iterator of a list by applying an iter() on it.

>>> iter_a = iter(a)
>>> print(iter_a)
<list_iterator object at 0x03FE8FB0>

>>> next(iter_a)
1

So do this with your code:

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

First line of somefile.txt (my file):

>>> %Run 'some file iterator.py'
aaaaaaa

Now do:

>>> dir(lines_iter)

You will see:

 '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', 

See? It now has a next method!