9

If I define the __iter__ method as follows, it won't work:

class A:

    def __init__(self):
        self.__iter__ = lambda: iter('text')


for i in A().__iter__():
    print(i)

iter(A())

Result:

t
e
x
t
Traceback (most recent call last):
  File "...\mytest.py", line 10, in <module>
    iter(A())
TypeError: 'A' object is not iterable

As you can see, calling A().__iter__() works, but A() is not iterable.

However if I define __iter__ for the class, then it will work:

class A:

    def __init__(self):

        self.__class__.__iter__ = staticmethod(lambda: iter('text'))
        # or:
        # self.__class__.__iter__ = lambda s: iter('text')


for i in A():
    print(i)

iter(A())

# will print:
# t
# e
# x
# t

Does anyone know why python has been designed like this? i.e. why __iter__ as instance variable does not work? Don't you find it unintuitive?

AXO
  • 8,198
  • 6
  • 62
  • 63
  • I think the way you try to do it in very unintuitive, why don't you just override __iter__ as a method ? http://stackoverflow.com/questions/4019971/how-to-implement-iter-self-for-a-container-object-python – PyNico Apr 11 '17 at 13:02
  • 1
    Seems to be an old-style vs new-style class issue. Your code does not work with new-style classes. – timgeb Apr 11 '17 at 13:21
  • @PyNico In the particular case that I'm currently working on, I prefer to define `__iter__` inside `__init__` because it's implementation depends on the instantiation arguments. – AXO Apr 11 '17 at 13:31

1 Answers1

5

It is done by design. You can find the thorough description here: https://docs.python.org/3/reference/datamodel.html#special-method-lookup

Short answer: the special method must be set on the class object itself in order to be consistently invoked by the interpreter.

Long answer: the idea behind this is to speed up well-known constructions. In your example:

class A:
    def __init__(self):
        self.__iter__ = lambda: iter('text')

How often are you going to write a code like this in real life? So, what Python does - it skips a dictionary lookup of the instance, i.e. iter(A()) simply does not "see" that self.__iter__, which is actually self.__dict__['__iter__'] in this case.

It also skips all the __getattribute__ instance and metaclass lookup gaining a significant speedup.

Zaur Nasibov
  • 22,280
  • 12
  • 56
  • 83
  • I've rewritten the answer to make it more comprehensible. Please tell if you have any questions, or something is not clear. – Zaur Nasibov Apr 11 '17 at 14:07
  • This is probably also related to why `(1, 2, 3)[2]` is much more faster than direct calling of `(1, 2, 3).__getitem__(2)` (in the latter python also looks inside instance attributes). Great answer, thank you so much. – AXO Apr 11 '17 at 15:26