Why does this next method I dynamically bound to an instance of a class fail and return a non-iterator object?
from collections import Iterator
from collections import Iterable
from types import MethodType
def next(cls):
if cls.start < cls.stop:
cls.start += 1
return cls.start
else:
raise StopIteration
class Foo(object):
start, stop = 0, 5
def __iter__(self):
return self
if __name__ == "__main__":
foo = Foo()
setattr(foo, 'next', MethodType(next, foo, Foo))
print hasattr(foo, "next")
if isinstance(foo, Iterable):
print "iterable"
if isinstance(foo, Iterator):
print "iterator"
for i in foo:
print i
Output:
iterable
True
TypeError: iter() returned non-iterator of type 'Foo'
It worked properly, when I did setattr(Foo, 'next', classmethod(next)).
This is the code that failed, so let’s take a look at what happens internally there, diving into the source for some Python internals!
When
for i in foois compiled, the generated bytecode will contain theGET_ITERopcode which is responsible of convertingfoointo an iterable.GET_ITERcauses aPyObject_GetItercall on the object which is the actual implementation that provides the iterable. So let’s take a look at what it does:As you can see (if you understand some basic C at least), the underlying type is looked up from the object first (
o->ob_type), and then its iter function is read (t->tp_iter).Since you have implemented an
__iter__function on the type, this function does exist, so we get to theelsecase in the code above, which runs theiterfunction on the objecto. The result is non-null, but we still get the “returned non-iterator” message, so thePyIter_Check(res)appears to have failed. So let’s take a look at what that does:So this one essentially checks if the type (
ob_type) of the passed object has a non-nullnextmethod (tp_iternext) which does not happen to be the not-implemented next function.Check closely where that check happens though: On the result’s type, not the result itself. The
fooobject does have anextfunction, but its typeFoodoes not have one.… or more explicitly …
… does only set the bound
nextmethod on the instance but not on the type itself. So the above C code will fail consuming it as an iterable.If you were to set your
nextfunction on the type instead, it would work fine:This is the reason why your attempt with
classmethodworked: You set the function on the type instead of its concrete instance.