2015-04-12 23 views
6

Biliyorum, bu yanlış, ama mümkün mü? .__iter__ yöntemi bir yineleyici döndürdüğünde bir nesnenin yinelenebilir olarak kabul edildiğini düşündüm? Peki bu neden çalışmıyor?Forbiddenfruit with int iterable

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

intyapar artık __iter__ yöntemi var gibi:

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

Açık olmanın kötü olacağını unutmayın. k: 'liste ((5) .__ iter __())' '[0, 1, 2, 3, 4]' – jonrsharpe

+0

Bir hatayı kütüphaneye bildirmeyi düşünün. –

+1

İlginç. Ayrıca x = (5) .__ iter __() 'yapabilirim ve sonra' next' olarak adlandırırım, beklendiği gibi davranır. – L3viathan

cevap

5

bir for döngünün sökme İşte:

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 

yüzden GET_ITER işlem kodu istiyoruz. PyObject_GetIter kullanan

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

:

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

Bu ilk kontroller t->tp_iter butlan için. Normalde bir sınıf üzerinde bir öznitelik ayarladığınızda

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 

, bu PyType_Type->setattro çağırır:

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

update_slot Bkz

Şimdi burada her şey tıklayın kılan şey? Bu, yuvaya gider ve günceller, böylece GET_ITER numaralı çağrı X üzerinde tp->tp_iter'a çarpacaktır. Ancak, forbiddenfruit bu işlemi atlar ve sadece bir sözlüğü sınıfa enjekte eder. Bu PyLong_Type onun varsayılan tutar anlamına gelir: Yani

if (f == NULL) 

tetiklendiğinde

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

, (bir dizi olmadığından)

if (PySequence_Check(o)) 

başarısız olur ve sonra sadece

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