class foo: def __init__(self, data): self.data = data def __len__(self): return self.data如果我通过为data传入一个字符串来运行这个函数,那么在对这个类的实例调用len时会得到一个错误。特别是我得到'str' object cannot be interpreted as an integer。那么return中的__len__语句必须是整数吗?我想如果我重写它,它应该可以输出我想要的任何东西,那么为什么这是不可能的呢? 最佳答案 TL;博士在C级,Python将__len__插入一个特殊的槽中,该槽捕获对__len__的调用的输出,并对其进行一些验证以确保其正确无误。为了回答这个问题,我们必须深入了解在Python中调用len时引擎盖下发生的事情。首先,让我们建立一些行为。>>> class foo:... def __init__(self, data):... self.data = data... def __len__(self):... return self.data...>>> len(foo(-1))Traceback:...ValueError: __len__() should return >= 0>>> len(foo('5'))Traceback:...TypeError: 'str' object cannot be interpreted as an integer>>> len(foo(5))5当您调用len时,将调用C函数builtin_len。让我们看看这个。static PyObject *builtin_len(PyObject *module, PyObject *obj)/*[clinic end generated code: output=fa7a270d314dfb6c input=bc55598da9e9c9b5]*/{ Py_ssize_t res; res = PyObject_Size(obj); // <=== THIS IS WHAT IS IMPORTANT!!! if (res < 0 && PyErr_Occurred()) return NULL; return PyLong_FromSsize_t(res);}您将注意到PyObject_Size函数正在被调用-该函数将返回任意Python对象的大小。让我们在兔子洞里再往前走。Py_ssize_tPyObject_Size(PyObject *o){ PySequenceMethods *m; if (o == NULL) { null_error(); return -1; } m = o->ob_type->tp_as_sequence; if (m && m->sq_length) return m->sq_length(o); // <==== THIS IS WHAT IS IMPORTANT!!! return PyMapping_Size(o);}它检查类型是否定义了sq_length函数(序列长度),如果是,则调用它来获取长度。似乎在C级别,Python将所有定义__len__的对象分类为序列或映射(即使我们在Python级别不是这样认为的);在我们的例子中,Python将此类视为序列,因此它调用sq_length。先简单说一下:对于内置类型(比如list,set,等等),Python实际上并不调用函数来计算长度,而是访问存储在C结构中的值,这使得计算速度非常快。这些内置类型中的每一个都定义了如何通过将访问器方法分配给sq_length来访问它。让我们快速浏览一下how this is implemented for lists:static Py_ssize_tlist_length(PyListObject *a){ return Py_SIZE(a); // <== THIS IS A MACRO for (PyVarObject*) a->ob_size;}static PySequenceMethods list_as_sequence = { ... (lenfunc)list_length, /* sq_length */ ...};ob_size存储对象的大小(即列表中的元素数)。因此,当调用sq_length时,它被发送到list_length函数以获取ob_size的值。好吧,这就是内置类型的实现方式。。。对于我们这样的定制类,它是如何工作的?由于“dunder方法”(如foo)是特殊的,Python会在我们的类中检测它们并对它们进行特殊处理(特别是将它们插入特殊的槽中)。其中大部分在typeobject.c中处理。__len__函数被截取并分配给__len__插槽(就像内置的!)near the bottom of the file。SQSLOT("__len__", sq_length, slot_sq_length, wrap_lenfunc, "__len__($self, /)\n--\n\nReturn len(self)."),sq_length函数是我们最终可以回答您问题的地方。static Py_ssize_tslot_sq_length(PyObject *self){ PyObject *res = call_method(self, &PyId___len__, NULL); Py_ssize_t len; if (res == NULL) return -1; len = PyNumber_AsSsize_t(res, PyExc_OverflowError); // <=== HERE!!! Py_DECREF(res); if (len < 0) { // <== AND HERE!!! if (!PyErr_Occurred()) PyErr_SetString(PyExc_ValueError, "__len__() should return >= 0"); return -1; } return len;}这里有两点值得注意:如果返回一个负数,则会引发一个slot_sq_length并显示消息ValueError。这正是我试图呼叫"__len__() should return >= 0"时收到的错误!Python试图在返回之前将len(foo(-1))的返回值强制为__len__(Py_ssize_t是Py_ssize_t的有符号版本,它类似于一种特殊类型的整数,可以保证能够索引容器中的内容)。好的,让我们看看size_t的实现。这有点长,所以我会省略不相关的内容。Py_ssize_tPyNumber_AsSsize_t(PyObject *item, PyObject *err){ Py_ssize_t result; PyObject *runerr; PyObject *value = PyNumber_Index(item); if (value == NULL) return -1; /* OMITTED FOR BREVITY */这里的相关位是 >,Python用于将任意对象转换成适合索引的整数。这就是你问题的真正答案所在。我已经注释了一点。PyObject *PyNumber_Index(PyObject *item){ PyObject *result = NULL; if (item == NULL) { return null_error(); } if (PyLong_Check(item)) { // IS THE OBJECT ALREADY AN int? IF SO, RETURN IT NOW. Py_INCREF(item); return item; } if (!PyIndex_Check(item)) { // DOES THE OBJECT DEFINE __index__? IF NOT, FAIL. PyErr_Format(PyExc_TypeError, "'%.200s' object cannot be interpreted " "as an integer", item->ob_type->tp_name); return NULL; } result = item->ob_type->tp_as_number->nb_index(item); if (!result || PyLong_CheckExact(result)) return result; if (!PyLong_Check(result)) { // IF __index__ DOES NOT RETURN AN int, FAIL. PyErr_Format(PyExc_TypeError, "__index__ returned non-int (type %.200s)", result->ob_type->tp_name); Py_DECREF(result); return NULL; } /* Issue #17576: warn if 'result' not of exact type int. */ if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, "__index__ returned non-int (type %.200s). " "The ability to return an instance of a strict subclass of int " "is deprecated, and may be removed in a future version of Python.", result->ob_type->tp_name)) { Py_DECREF(result); return NULL; } return result;}根据您收到的错误,我们可以看到PyNumber_AsSsize_t没有定义PyNumber_Index。我们可以自己验证:>>> '5'.__index__()Traceback:...AttributeError: 'str' object has no attribute '__index__'
09-10 04:43
查看更多