如果 __richcmp__ 仅部分实现,我们应该引发 TypeError 吗?

Should we raise TypeError if __richcmp__ only partially implemented?

我为 Cython 扩展类型 (cdef class) 实现了方法 __richcmp__。有些比较案例是未定义的(例如<),所以我曾经为他们提出一个Exceptionas follows

def __richcmp__(Function self, Function other, op):
    if other is None:
        eq = False
    else:
        # guard against mixing managers
        assert self.manager == other.manager
        eq = (self.node == other.node)
    if op == 2:
        return eq
    elif op == 3:
        return not eq
    else:
        raise TypeError('Only `__eq__` and `__ne__` defined.')

我想要 pprint 这个 Cython class 实例的容器。 pprint attempts to compare them, except for a TypeError. My understanding is that as TypeError, pprint anticipates the case of undefined __lt__, or the case of different types of objects (see also Python docs).

但是,__richcmp__ 实施的,因此 Python 不会引发 TypeError。它调用 __richcmp__,我提出一个 Exceptionpprint 并没有忽略它。 Cython requires 实现了 __richcmp__,所以我没有选择只定义 __eq____ne__.

我更改了代码以引发 TypeError。似乎如果 Python 用 TypeError 传达 __lt__ 的缺失,那么我应该做同样的事情,以表示 __lt__ 不存在,尽管存在 __lt__整个 __richcmp__,这是使用 Cython 的副产品,而不是设计意图。

这个推理有道理吗?我应该提出另一种例外吗?在这种情况下,我是否正确解释了 TypeError 的含义?

是的。 Cython 将您的实现用作 C API tp_richcompare。该文件告诉你

If you want to implement a type for which only a limited set of comparisons makes sense (e.g. == and !=, but not < and friends), directly raise TypeError in the rich comparison function.

这给了你一个相当强烈的暗示,表明这是正确的做法。

@DavidW 提供了非常准确和有用的答案,谢谢。它指出了如何处理类似的未来问题:通过查看生成的 C 代码。我将此答案发布为后代的免费信息。

在生成的 cudd.c 文件中搜索,我们找到 class 签名:

static PyTypeObject __pyx_type_2dd_4cudd_Function = {
    PyVarObject_HEAD_INIT(0, 0)
    "dd.cudd.Function", /*tp_name*/
    sizeof(struct __pyx_obj_2dd_4cudd_Function), /*tp_basicsize*/
    0, /*tp_itemsize*/
    ...
    ...
    __pyx_pw_2dd_4cudd_8Function_13__richcmp__, /*tp_richcompare*/
    ...
    ...
    #if PY_VERSION_HEX >= 0x030400a1
    0, /*tp_finalize*/
    #endif
};

(点与我们的讨论无关。) PyTypeObjectCPython C API 定义。函数__pyx_pw_2dd后面定义

/* "dd/cudd.pyx":1556
 *         return Cudd_DagSize(self.node)
 * 
 *     def __richcmp__(Function self, Function other, op):             # <<<<<<<<<<<<<<
 *         if other is None:
 *             eq = False
 */

/* Python wrapper */
static PyObject *__pyx_pw_2dd_4cudd_8Function_13__richcmp__(PyObject *__pyx_v_self, PyObject *__pyx_v_other, int __pyx_arg_op); /*proto*/
static PyObject *__pyx_pw_2dd_4cudd_8Function_13__richcmp__(PyObject *__pyx_v_self, PyObject *__pyx_v_other, int __pyx_arg_op) {
PyObject *__pyx_v_op = 0;
PyObject *__pyx_r = 0;

找到第一个摘录的方法是首先在 C 文件中搜索感兴趣的 Python 行(这里是 Function.__richcmp__ 的签名),然后搜索 C 函数的调用者(虽然在这个讨论的指导下,我通过搜索 tp_richcompare 找到了它们)。

确认一下,我相信 Cython 生成了第一个摘录 Cython/Compiler/TypeSlots.py:

# Later -- synthesize a method to split into separate ops?
MethodSlot(richcmpfunc, "tp_richcompare", "__richcmp__", inherited=False), # Py3 checks for __hash__

有趣的是,那里的评论可能暗示将来用户将能够实现单独的比较器方法,从而不会直接遇到这个问题。