Hay un montón de lugares donde CPython tomar atajos sorprendentes sobre la base de la clase propiedades en lugar de ejemplo propiedades. Este es uno de esos lugares.
Aquí está un ejemplo sencillo que muestra el problema:
def DynamicNext(object):
def __init__(self):
self.next = lambda: 42
Y aquí es lo que sucede:
>>> instance = DynamicNext()
>>> next(instance)
…
TypeError: DynamicNext object is not an iterator
>>>
Ahora, cavando en el código fuente CPython (de 2.7.2), aquí está el aplicación de la orden interna next()
:
static PyObject *
builtin_next(PyObject *self, PyObject *args)
{
…
if (!PyIter_Check(it)) {
PyErr_Format(PyExc_TypeError,
"%.200s object is not an iterator",
it->ob_type->tp_name);
return NULL;
}
…
}
Y aquí está la implementatio n de PyIter_Check:
#define PyIter_Check(obj) \
(PyType_HasFeature((obj)->ob_type, Py_TPFLAGS_HAVE_ITER) && \
(obj)->ob_type->tp_iternext != NULL && \
(obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented)
La primera línea, PyType_HasFeature(…)
, es decir, después de expandirse en todas las constantes y macros y esas cosas, lo que equivale a DynamicNext.__class__.__flags__ & 1L<<17 != 0
:
>>> instance.__class__.__flags__ & 1L<<17 != 0
True
Así que, obviamente, no es cheque fallando ... ¿Qué debe significar que el próximo cheque - (obj)->ob_type->tp_iternext != NULL
- está fallando.
En Python, esta línea es más o menos (más o menos!) Equivalente a hasattr(type(instance), "next")
:
>>> type(instance)
__main__.DynamicNext
>>> hasattr(type(instance), "next")
False
Lo que obviamente falla porque el tipo DynamicNext
no tiene un método next
- únicos casos de ese tipo hacer.
Ahora, mi CPython foo es débil, así que voy a tener que empezar a hacer algunas conjeturas aquí ... Pero creo que son precisas.
Cuando se crea un tipo CPython (es decir, cuando el intérprete se evalúa primero el bloque de class
y la clase metaclase __new__
método se llama), los valores de PyTypeObject
estructura del tipo se inician al ... Así que si, cuando el DynamicNext
se crea el tipo, no existe el método next
, el campo tp_iternext
se establecerá en NULL
, lo que hará que PyIter_Check
devuelva falso.
Ahora bien, como señala el Glenn, esto es casi seguro que un error en CPython ... Especialmente teniendo en cuenta que la corrección sería sólo el rendimiento impacto cuando ya sea el objeto que está siendo probado no es iterable o dinámicamente asigna un método next
(muy aproximadamente):
#define PyIter_Check(obj) \
(((PyType_HasFeature((obj)->ob_type, Py_TPFLAGS_HAVE_ITER) && \
(obj)->ob_type->tp_iternext != NULL && \
(obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented)) || \
(PyObject_HasAttrString((obj), "next") && \
PyCallable_Check(PyObject_GetAttrString((obj), "next"))))
Editar: después de un poco de excavación, la solución no sería tan sencilla, ya que al menos algunas partes del código suponen que, si PyIter_Check(it)
vuelve true
, entonces existirá *it->ob_type->tp_iternext
... Lo cual no es necesario con dificultad el caso (es decir, porque la función next
existe en la instancia, no el tipo).
SO! Es por eso que suceden cosas sorprendentes cuando intenta iterar sobre una instancia de estilo nuevo con un método next
dinámicamente asignado.
¿Ha provocado un error en http://bugs.python.org? –