Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to use __iter__() vs iter()?

Tags:

python

Let's say I have a class which implements an __iter__() function, is it preferred to use iter(obj) or calling obj.__iter__() directly? Are there any real differences besides having to type 5 characters less with the magic function?

In contrast: For next() and __next__() I can see an advantage for having a default value with the magic function.

like image 319
Ruehri Avatar asked Nov 27 '18 15:11

Ruehri


1 Answers

The difference is mostly just convenience. It's less typing and less symbols to read, and so faster to read. However, the various builtin functions (eg. iter, len et al.) usually do a little type checking to catch errors early. If you wrote a customer __iter__ method and it returned 2, then invoking obj.__iter__() wouldn't catch that, but iter(obj) throws a type error. eg.

>>> class X:
    def __iter__(self):
        return 2

>>> x = X()
>>> x.__iter__()
2
>>> iter(x)
Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    iter(x)
TypeError: iter() returned non-iterator of type 'int'

iter also implements the iterator protocol for objects that have no __iter__, but do implement the sequence protocol. That is, they have a __getitem__ method which implements a sequence starting at index 0 and raises an IndexError for indexes not in bounds. This is an older feature of python and not really something new code should be using. eg.

>>> class Y:
    def __getitem__(self, index):
        if 0 <= index < 5:
            return index ** 2
        else:
            raise IndexError(index)

>>> list(iter(Y()))  # iter not strictly needed here
[0, 1, 4, 9, 16]

When should you use __iter__? This might not be so relevant to __iter__, but if you need access to the implementation of method that the parent class uses then it is best to invoke such methods in the style super().__<dunder_method>__() (using Python 3 style super usage). eg.

>>> class BizzareList(list):
    def __iter__(self):
        for item in super().__iter__():
            yield item * 10

>>> l = BizzareList(range(5))
>>> l  # normal access
[0, 1, 2, 3, 4]
>>> l[0]  # also normal access
0
>>> tuple(iter(l))  # iter not strictly needed here
(0, 10, 20, 30, 40)
like image 165
Dunes Avatar answered Sep 27 '22 23:09

Dunes