Parsing a for loop:
import dis dis.dis("for _ in _: pass")
So, we need the operation code GET_ITER .
TARGET(GET_ITER) { PyObject *iterable = TOP(); PyObject *iter = PyObject_GetIter(iterable); Py_DECREF(iterable); SET_TOP(iter); if (iter == NULL) goto error; PREDICT(FOR_ITER); DISPATCH(); }
Uses PyObject_GetIter :
PyObject * PyObject_GetIter(PyObject *o) { PyTypeObject *t = o->ob_type; getiterfunc f = NULL; 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; } }
This first checks t->tp_iter for invalidity.
Now, here is what all the clicking does:
class X: pass X.__iter__ = lambda x: iter(range(10)) list(X()) #>>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] from forbiddenfruit import curse class X: pass curse(X, "__iter__", lambda x: iter(range(10))) list(X()) #>>> Traceback (most recent call last): #>>> File "", line 16, in <module> #>>> TypeError: 'X' object is not iterable
When you usually set an attribute in a class, it calls PyType_Type->setattro :
static int type_setattro(PyTypeObject *type, PyObject *name, PyObject *value) { if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { PyErr_Format( PyExc_TypeError, "can't set attributes of built-in/extension type '%s'", type->tp_name); return -1; } if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0) return -1; return update_slot(type, name); }
See update_slot ? This happens and updates the slot, so the next call to GET_ITER will hit tp->tp_iter on X However forbiddenfruit bypasses this process and simply introduces the dictionary into the class. This means that PyLong_Type retains its default value:
PyTypeObject PyLong_Type = { ... 0, ... };
So,
if (f == NULL)
starts up
if (PySequence_Check(o))
doesn't work (since this is not a sequence) and then just
return type_error("'%.200s' object is not iterable", o);