Ruehri
Ruehri

Reputation: 858

When to use __iter__() vs iter()?

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.

Upvotes: 7

Views: 3038

Answers (2)

PIEthonista
PIEthonista

Reputation: 1

The iter() function (which in turn calls the iter() method) is a python built-in function that returns an iterator from them. You can opt to use iter(obj) or obj.__iter__(), they are simply the same thing. Its just that __iter__() is a user-defined function and calling it will just do what the written code asks it to do. On the other hand, calling the iter() function will in turn call the user-defined __iter__() function and run the same code. However, iter() will also run a built-in 'type' checking, in which if the customized __iter__() doesn't return an iterator object, it would throw an error. *The same thing applies to next() & __next__().

Thus, iter(obj) ~= obj.__iter__(), next(obj) ~= obj.__next__()

Note that __iter__() and __next__() must be defined by the user when creating a class if the object os intended to be an iterator.

Source: https://www.programiz.com/python-programming/iterator

Upvotes: 0

Dunes
Dunes

Reputation: 40713

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)

Upvotes: 12

Related Questions