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__
语句必须是整数吗?我想如果我压倒它,它应该能够输出我想要的任何东西,为什么这不可能呢?
答案 0 :(得分:5)
<强> TL; DR 强>
在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_t
PyObject_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_t
list_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
的值。
好的,这就是内置类型的完成方式......对于像foo
这样的自定义类,它是如何工作的?由于“dunder方法”(例如__len__
)是特殊的,Python会在我们的类中检测它们并特别处理它们(特别是将它们插入特殊的插槽中)。
大部分内容都是在typeobject.c中处理的。截获__len__
函数并将其分配到sq_length
广告位(就像内置版!)near the bottom of the file。
SQSLOT("__len__", sq_length, slot_sq_length, wrap_lenfunc,
"__len__($self, /)\n--\n\nReturn len(self)."),
slot_sq_length
功能是我们最终回答您的问题的地方。
static Py_ssize_t
slot_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;
}
这里有两点需要注意:
ValueError
,并显示消息"__len__() should return >= 0"
。这是完全我尝试拨打len(foo(-1))
时收到的错误!__len__
的返回值强制转换为Py_ssize_t
(Py_ssize_t
是签名版本的size_t
,其中就像一个特殊类型的整数,保证能够索引容器中的东西。)好的,让我们来看看PyNumber_AsSsize_t
的实现。这有点长,所以我会省略不相关的东西。
Py_ssize_t
PyNumber_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 */
这里的相关位在PyNumber_Index
中,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;
}
根据您收到的错误,我们可以看到'5'
未定义__index__
。我们可以为自己验证:
>>> '5'.__index__()
Traceback:
...
AttributeError: 'str' object has no attribute '__index__'