Built in iter() function and for statement

Viewed 397

I am trying to understand how the for x in y statement works in python. I found the documentation here: https://docs.python.org/3/reference/compound_stmts.html#for. It says that the expression y is evaluated once and must yield an iterable object.

The following code prints the numbers 1,2,3,4,5 even though my class does not implement __iter__ (which is my understanding of being an iterable).

class myclass:
    def __init__(self):
        self.x = [1,2,3,4,5]
    def __getitem__(self,index):
        return self.x[index]
m = myclass()
for i in m:
    print(i)

I know that there is a built-in method iter() that returns an iterator for a sequence object using its .__getitem__() function and a counter that starts at 0.

My guess is that python is calling the iter() function on the expression y in the for x in y statement. So it is converting my object that implements .__getitem__ into an iterator, and when my object raises a IndexError exception during the .__getitem__ call, the iterator turns this into a StopIteration exception, and the for loop ends.

Is this correct? Right or wrong, is this explained in the documentation somewhere, or do I need to go look inside the source code of the implementation?

2 Answers

According to PEP 234, which was helpfully linked in the comments above,

iter(obj) calls PyObject_GetIter(obj).

It has the following to say about for loops:

The Python bytecode generated for for loops is changed to use new opcodes, GET_ITER and FOR_ITER, that use the iterator protocol rather than the sequence protocol to get the next value for the loop variable. This makes it possible to use a for loop to loop over non-sequence objects that support the tp_iter slot. Other places where the interpreter loops over the values of a sequence should also be changed to use iterators.

Finally, https://docs.python.org/3/library/dis.html#opcode-GET_ITER explains that GET_ITER is equivalent to calling iter.

Putting this together, it seems that the for loop behaves the same as the built-in iter function.

Happy Pythoning.

getitem was the only method before Python 2.2 version to iterate loops on iterators. In Pyhton 2.2 version iter method was introduced. In getitem method, index is automatically passed as 0 ie 0 index and increase by 1 every time loop runs iterates. We exit from loop once IndexError raises. To have the backward compatibility getitem method is still not deprecated (Till Python version 3.8.5). So now firstly loop searches iter method is searched in iterable and if this method is not present then getitem method is searched. So as long back Python 2.2 came, so today developers prefer to use iter and next methods, which gives a clear cut understanding about iterable and iterator.

You can take one more example of getitem along with your example to understand iterator and iterable as follows.

class myclass:
    def __init__(self):
        pass
    def __getitem__(self,index):
        return index
m = myclass()
for i in m:
    print(i)

Above code will create an infinite loop because first index value will be 0 then 1 then 2 and so on and no error will be raised.

Output of above code:

0
1
2
3
4
5
6
7
8
9
10
11
.
.
.
.

Thanks for sharing the question.

Related