我下面有一个简单的类,
class MyClass(int):
def __index__(self):
return 1
Run Code Online (Sandbox Code Playgroud)
根据operator.index文档,
operator.index(a)返回一个转换为整数的值。相当于
a.__index__()
但是当我使用operator.index与MyClass实例,我得到了100,而不是1(我越来越1如果我用a.__index__())。这是为什么?。
>>> a = MyClass(100)
>>>
>>> import operator
>>> print(operator.index(a))
100
>>> print(a.__index__())
1
Run Code Online (Sandbox Code Playgroud)
这实际上似乎是 cpython 中根深蒂固的问题。如果查看 的源代码operator.py,可以看到 的定义index:
def index(a):
"Same as a.__index__()."
return a.__index__()
Run Code Online (Sandbox Code Playgroud)
那么……为什么不等价呢?它实际上是在调用__index__. 好吧,在源代码的底部,有罪魁祸首:
try:
from _operator import *
except ImportError:
pass
else:
from _operator import __doc__
Run Code Online (Sandbox Code Playgroud)
它使用本机_operator模块覆盖定义。事实上,如果你把它注释掉(通过修改实际的库或制作你自己的假operator.py* 并导入它),它就可以工作。所以,我们可以找到原生_operator库的源代码,并查看相关部分:
static PyObject *
_operator_index(PyObject *module, PyObject *a)
{
return PyNumber_Index(a);
}
Run Code Online (Sandbox Code Playgroud)
因此,它是PyNumber_Index函数的包装器。PyNumber_Index是一个包装器_PyNumber_Index,所以我们可以看看:
PyObject *
_PyNumber_Index(PyObject *item)
{
PyObject *result = NULL;
if (item == NULL) {
return null_error();
}
if (PyLong_Check(item)) {
Py_INCREF(item);
return item;
}
if (!_PyIndex_Check(item)) {
PyErr_Format(PyExc_TypeError,
"'%.200s' object cannot be interpreted "
"as an integer", Py_TYPE(item)->tp_name);
return NULL;
}
result = Py_TYPE(item)->tp_as_number->nb_index(item);
if (!result || PyLong_CheckExact(result))
return result;
if (!PyLong_Check(result)) {
PyErr_Format(PyExc_TypeError,
"__index__ returned non-int (type %.200s)",
Py_TYPE(result)->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.",
Py_TYPE(result)->tp_name)) {
Py_DECREF(result);
return NULL;
}
return result;
}
PyObject *
PyNumber_Index(PyObject *item)
{
PyObject *result = _PyNumber_Index(item);
if (result != NULL && !PyLong_CheckExact(result)) {
Py_SETREF(result, _PyLong_Copy((PyLongObject *)result));
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
您甚至可以在它调用之前看到nb_index(C 名称为__index__),它调用PyLong_Check参数,如果它为真,它只返回没有修改的项目。PyLong_Check是一个检查长子类型的宏(int在python中是一个PyLong):
#define PyLong_Check(op) \
PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LONG_SUBCLASS)
#define PyLong_CheckExact(op) Py_IS_TYPE(op, &PyLong_Type)
Run Code Online (Sandbox Code Playgroud)
所以,基本上,外卖是无论出于何种原因,可能是为了速度,int 子类不会__index__ 调用它们的方法,而是只是获取_PyLong_Copy结果返回值,但仅在本机_operator模块中,而不是在非-本机operator.py。这种实现的冲突以及文档中的不一致使我相信这是一个问题,无论是在文档中还是在实现中,您可能希望将其作为一个问题提出。
这可能是一个文档而不是一个实现问题,因为 cpython 习惯于牺牲正确性以换取速度:(nan,) == (nan,)但是nan != nan.
* 您可能必须将其命名为类似的名称,fake_operator.py然后将其导入import fake_operator as operator
这是因为您的类型是 int 子类。__index__不会被使用,因为实例已经是一个整数。这是设计使然,不太可能被视为 CPython 中的错误。PyPy 的行为相同。
static PyObject *
_operator_index(PyObject *module, PyObject *a)
/*[clinic end generated code: output=d972b0764ac305fc input=6f54d50ea64a579c]*/
{
return PyNumber_Index(a);
}
Run Code Online (Sandbox Code Playgroud)
注意operator.pyPython 代码一般不使用,此代码只是在编译_operator模块不可用的情况下的回退。这就解释了为什么结果a.__index__()不同。
在abstract.c,在相关PyLong_Check部分之后裁剪:
/* Return an exact Python int from the object item.
Raise TypeError if the result is not an int
or if the object cannot be interpreted as an index.
*/
PyObject *
PyNumber_Index(PyObject *item)
{
PyObject *result = _PyNumber_Index(item);
if (result != NULL && !PyLong_CheckExact(result)) {
Py_SETREF(result, _PyLong_Copy((PyLongObject *)result));
}
return result;
}
...
/* Return a Python int from the object item.
Can return an instance of int subclass.
Raise TypeError if the result is not an int
or if the object cannot be interpreted as an index.
*/
PyObject *
_PyNumber_Index(PyObject *item)
{
PyObject *result = NULL;
if (item == NULL) {
return null_error();
}
if (PyLong_Check(item)) {
Py_INCREF(item);
return item; /* <---- short-circuited here */
}
...
}
Run Code Online (Sandbox Code Playgroud)
的文档operator.index不准确,因此这可能被视为一个次要的文档问题:
>>> import operator
>>> operator.index.__doc__
'Same as a.__index__()'
Run Code Online (Sandbox Code Playgroud)
那么,为什么不__index__考虑整数呢?可能的答案可以在PEP 357 中标题为Speed的讨论部分下找到:
实现不应该减慢 Python 的速度,因为用作索引的整数和长整数将在相同数量的指令中完成。唯一的变化是以前产生错误的内容现在可以接受了。
我们不想减慢使用整数切片的最常见情况,nb_index每次都必须检查一个插槽。
| 归档时间: |
|
| 查看次数: |
252 次 |
| 最近记录: |