2015-04-12 11 views
6

Wiem, to jest złe, ale czy to możliwe? Myślałem, że obiekt jest uznawany za iteracyjny, gdy jego metoda .__iter__ zwróciła iterator? Dlaczego to nie działa?Zrobić intencję z forbiddenfruit

>>> 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 

introbi wydają się mieć metodę __iter__ teraz:

>>> int(5).__iter__ 
<bound method int.__iter__ of 5> 
+1

Należy pamiętać, że bycie wyraźnym będzie gorsze k: 'lista ((5) .__ iter __())' to '[0, 1, 2, 3, 4]' – jonrsharpe

+0

Rozważ zgłoszenie błędu do biblioteki. –

+1

Interesujące. Mogę też zrobić 'x = (5) .__ iter __()', a następnie wywołać 'next' na nim, zachowuje się zgodnie z oczekiwaniami. – L3viathan

Odpowiedz

5

demontaż z for pętli:

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 

Więc chcemy GET_ITER opcodu.

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(); 
} 

który wykorzystuje 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; 
    } 
} 

To pierwsze kontrole t->tp_iter dla nieważności.

Teraz tutaj jest rzecz, która sprawia, że ​​wszystko kliknij:

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 

Gdy ustawisz atrybut w klasie normalnie, wywołuje 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); 
} 

Zobacz update_slot? To idzie i aktualizuje slot, więc następne połączenie z GET_ITER trafi tp->tp_iter na X. Jednak forbiddenfruit omija ten proces i po prostu wprowadza słownik do klasy. Oznacza to, że PyLong_Type zachowuje domyślnie:

PyTypeObject PyLong_Type = { 
    ... 
    0,           /* tp_iter */ 
    ... 
}; 

Więc

if (f == NULL) 

zostanie uruchomiony,

if (PySequence_Check(o)) 

nie powiedzie się (ponieważ nie jest to sekwencja), a potem to tylko

return type_error("'%.200s' object is not iterable", o); 
Powiązane problemy