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 ?
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With