Make int iterable with forbidden fruit

I know this is wrong, but is it possible? I thought an object is considered iterable when its .__iter__ method returns an iterator? So why is this not working?

 >>> from forbiddenfruit import curse >>> def __iter__(self): ... for i in range(self): ... yield i >>> curse(int, "__iter__", __iter__) >>> for x in 5: ... print x ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not iterable 

int now has a __iter__ method:

 >>> int(5).__iter__ <bound method int.__iter__ of 5> 
+6
source share
1 answer

Parsing a for loop:

 import dis dis.dis("for _ in _: pass") #>>> 1 0 SETUP_LOOP 14 (to 17) #>>> 3 LOAD_NAME 0 (_) #>>> 6 GET_ITER #>>> >> 7 FOR_ITER 6 (to 16) #>>> 10 STORE_NAME 0 (_) #>>> 13 JUMP_ABSOLUTE 7 #>>> >> 16 POP_BLOCK #>>> >> 17 LOAD_CONST 0 (None) #>>> 20 RETURN_VALUE 

So, we need the operation code GET_ITER .

 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); 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, /* tp_iter */ ... }; 

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); 
+5
source

Source: https://habr.com/ru/post/985016/


All Articles