# Why 'int' object is not iterable - because tp_iter is NULL

Recently I was curious about how int object is implemented in CPython and researched the iterable error for int. My code sample is:

$ cat test_iterator.py 
x = 3 
for i in x: 
    pass

It is obvious that we can not go through integer but for CPython this code will be compiled successfully - in contrast it will fail at runtime:

$ ~/REPOS/cpython/debug_3.8/python test_iterator.py
Traceback (most recent call last): 
    File "test_iterator.py", line 2, in <module>;
        for i in x:
TypeError: 'int' object is not iterable

So why?

For this let’s see the opcodes:

$ ~/REPOS/cpython/debug_3.8/python -m dis test_iterator.py
  1           0 LOAD_CONST               0 (3)
              2 STORE_NAME               0 (x)
 
  2           4 LOAD_NAME                0 (x)
              6 GET_ITER
        >>    8 FOR_ITER                 4 (to 14)
             10 STORE_NAME               1 (i)
 
  3          12 JUMP_ABSOLUTE            8
        >>   14 LOAD_CONST               1 (None)
             16 RETURN_VALUE

It seems to be GET_ITER should get the iterable object for us.

Open up Python/ceval.c in source and search for GET_ITER – you will find the target as part of huge switch statement - ceval.c#L2859

TARGET(GET_ITER) {
    /* before: [obj]; after [getiter(obj)] */
    PyObject *iterable = TOP();
    PyObject *iter = PyObject_GetIter(iterable);
    Py_DECREF(iterable);
    SET_TOP(iter);
    if (iter == NULL)
    goto error;
    PREDICT(FOR_ITER);
    PREDICT(CALL_FUNCTION);
    DISPATCH();
 }

There is a call for PyObject_GetIter() which is in Objects/abstract.c - abstract.c#L2542 (opens new window)

PyObject *
PyObject_GetIter(PyObject *o)
{
    PyTypeObject *t = o->ob_type;
    getiterfunc f;
    f = t->tp_iter;
    if (f == NULL) {
    if (PySequence_Check(o))
        return PySeqIter_New(o);
        return type_error("'%.200s' object is not iterable", o);
    }
    else {
    PyObject *res = (*f)(o);
    if (res != NULL && !PyIter_Check(res)) {
        PyErr_Format(PyExc_TypeError,
        "iter() returned non-iterator "
        "of type '%.100s'",
        res->ob_type->tp_name);
        Py_DECREF(res);
        res = NULL;
    }
    return res;
    }
}

As you see there is an error similar to our error at runtime ->

type_error("'%.200s' object is not iterable", o)

This will only true if f is NULL:

if (f == NULL) {
 if (PySequence_Check(o))
    return PySeqIter_New(o);
    return type_error("'%.200s' object is not iterable", o);
 }

And what about f?

PyTypeObject *t = o->ob_type;
 getiterfunc f;
 f = t->tp_iter;

So an int object has tp_iter which must be NULL for int object. Let’s see the type definition of int object, i.e PyLong_Type. We can see it from debugger:

(gdb) p PyLong_Type->tp_iter
$2 = (getiterfunc) 0x0

As well as from source code -> longobject.c#L5483 (opens new window)

Okay then we can convince our soul that List object is iterable because tp_iter is not NULL? Yes sure!

(gdb) p PyList_Type->tp_iter
$3 = (getiterfunc) 0x5555555d3277 <list_iter>
(gdb) p PyTuple_Type->tp_iter
$4 = (getiterfunc) 0x55555560a97e <tuple_iter>
(gdb) p PySet_Type->tp_iter
$5 = (getiterfunc) 0x555555602278 <set_iter>

That is the fundamental idea of checking if some object is iterable or not. Cool.