Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return a non iterator from __iter__

Tags:

python

class test(object):
    def __init__(self):
        pass
    def __iter__(self):
        return "my string"

o = test()
print iter(o)

Why does this give a traceback ?

$ python iter_implement.py
Traceback (most recent call last):
  File "iter_implement.py", line 9, in <module>
    print iter(o)
TypeError: iter() returned non-iterator of type 'str'

I was hoping __iter__ to just return the string in this case. When and why is it detected that the returned object is not an iterator object ?

like image 361
Ankur Agarwal Avatar asked Nov 29 '22 22:11

Ankur Agarwal


2 Answers

The code can be repaired by adding an iter() call:

class test(object):
    def __init__(self):
        pass
    def __iter__(self):
        return iter("my string")

Here is a sample run:

>>> o = test()
>>> iter(o)
<iterator object at 0x106bfa490>
>>> list(o)
['m', 'y', ' ', 's', 't', 'r', 'i', 'n', 'g']

The reason for the original error is that the API for __iter__ purports to return an actual iterator. The iter() function checks to make sure the contract is fulfilled.

Note, this kind of error checking occurs in other places as well. For example, the len() function checks to make sure a __len__() method returns an integer:

>>> class A:
        def __len__(self):
            return 'hello'

>>> len(A())
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    len(A())
TypeError: 'str' object cannot be interpreted as an integer
like image 116
Raymond Hettinger Avatar answered Dec 05 '22 14:12

Raymond Hettinger


str is an iterable but not an iterator, subtle but important difference. See this answer for an explanation.

You want to return an object with __next__ (or just next if py2) which is what str returns when is iterated.

def __iter__(self):
      return iter("my string")

str does not implement __next__

In [139]: s = 'mystring'

In [140]: next(s)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-140-bc0566bea448> in <module>()
----> 1 next(s)

TypeError: 'str' object is not an iterator

However calling iter returns the iterator, which is what looping calls:

In [141]: next(iter(s))
Out[141]: 'm'

You get the same problem returning anything without a __next__ (or next in py2) method

You can use a generator, which itself has __iter__ that returns self:

def gen():
    yield 'foo'

gg = gen()

gg is gg.__iter__()
True

gg.__next__()
'foo'

class Something:
     def __iter__(self):
         return gen()

list(Something())
['foo']

Or a class where you implement __next__ yourself, like this class similar to the one on the Ops post (you also have to handle StopIteration which stops the loop)

class test:
    def __init__(self, somestring):
        self.s = iter(somestring)

    def __iter__(self):
        return self

    def __next__(self):
        return next(self.s) ## this exhausts the generator and raises StopIteration when done.

In [3]: s = test('foo')

In [4]: for i in s:
   ...:     print(i)
   ...:
f
o
o
like image 39
salparadise Avatar answered Dec 05 '22 13:12

salparadise