How come an object that implements __iter__ is not recognized as iterable?

iterable, python, python-3.x

Solution

Basically because your class just doesn't have a real `__iter__` method:

>>> hasattr(IterOrNotIter, '__iter__')
False

So it doesn't qualify as iterator because the actual check for `__iter__` checks for the existence instead of assuming it's implemented. So workarounds with `__getattr__` or `__getattribute__` (unfortunatly) don't work.

This is actually mentioned in the documentation for `__getattribute__`:

Note

This method may still be bypassed when looking up special methods as the result of implicit invocation via language syntax or built-in functions. See Special method lookup.

The latter section also explains the why:

Bypassing the `__getattribute__()` machinery in this fashion provides significant scope for speed optimisations within the interpreter, at the cost of some flexibility in the handling of special methods (the special method must be set on the class object itself in order to be consistently invoked by the interpreter).

Emphasis mine.

Problem

Let's say you work with a wrapper object: ``` class IterOrNotIter: def __init__(self): self.f = open('/tmp/toto.txt') def __getattr__(self, item): try: return self.__getattribute__(item) except AttributeError: return self.f.__getattribute__(item) ``` This object implements `__iter__`, because it passes any call to it to its member `f`, which implements it. Case in point: ``` >>> x = IterOrNotIter() >>> x.__iter__().__next__() 'Whatever was in /tmp/toto.txt\n' ``` According to the documentation (https://docs.python.org/3/library/stdtypes.html#iterator-types), IterOrNotIter should thus be iterable. However, the Python interpreter does not recognize an `IterOrNotIter` object as actually being iterable: ``` >>> x = IterOrNotIter() >>> for l in x: ... print(l) ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'IterOrNotIter' object is not iterable ``` Whereas this works: ``` >>> x = IterOrNotIter() >>> for l in x.f: ... print(l) ... Whatever was in /tmp/toto.txt ``` I don't understand why.

Original source