实现数值方法始终返回NotImplemented

时间:2012-09-16 13:59:28

标签: python python-c-api

我正在编写一个新的扩展类型,但是我在设置数值运算时遇到了问题(例如加法/减法/乘法)。我已设法设置一些就地操作,而不调用正常操作。

例如,我有以下功能:

static PyObject *
MyType_Mul(PyObject *v, PyObject *w)
{
    PyErr_SetString(PyExc_ValueError, "testing");
    return NULL;
}

我将其设置为数字方法:

static PyNumberMethods my_type_as_number = {
    0,  /* nb_add */
    0,  /* nb_sub */
    (binaryfunc)MyType_Mul,  /* nb_mul */
    ...
    0,  /* nb_in_place_add */
    0,  /* nb_in_place_sub */
    (binaryfunc)MyType_Mul,  /* nb_in_place_mul */
    ...
};

现在,当我尝试使用我的类型时,我会遇到这种行为:

>>> from mytype import MyType
>>> a = MyType()
>>> a * 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for *: 'mytype.MyType' and 'int'
>>> 2 * a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for *: 'int' and 'mytype.MyType'

但是如果我使用就地操作符:

>>> a *= 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: testing

如果我在对象上使用dir(),我可以看到__mul____rmul__方法(这意味着python看到它们),但似乎根本没有调用它们。 使用a.__mul__(2)会返回NotImplemented

此外:

>>> a.__mul__
<method-wrapper '__mul__' of mytype.MyType object at 0x7fc2ecc50468>
>>> a.__imul__
<method-wrapper '__imul__' of mytype.MyType object at 0x7fc2ecc50468>

所以,正如你所看到的,他们完全同样的事情。

发生了什么事?为什么同样的功能适用于就地操作员而不适用于“普通”操作员?我还以为我可能使用了错误的插槽,但我仔细检查了它是正确的,并且还将其设置为nb_addnb_sub等不起作用。

1 个答案:

答案 0 :(得分:3)

感谢nneonneo的评论,我明白了什么是错的。基本上我忘了设置Py_TPFLAGS_CHECKTYPES标志。

在我给出的描述中有一些关于这种缺席的线索:

  1. 这些方法是相同的对象,但它对就地操作的行为不同
  2. 在评论中我也说过,例如,执行a*a会产生正确的结果,而a*different-type却没有。
  3. 这显然意味着解释器在进行非就地操作时,正在检查参数的类型,如果类型为MyType则调用我的函数,否则返回NotImplemented

    在文档中搜索一下,很容易看出这是数值方法的默认行为。

    如果参数类型不是同一个类,则假定该操作未实现。

    要允许同时“操作”不同类型,您必须在Py_TPFLAGS_CHECKTYPES中设置MyType标记:

    static PyTypeObject MyType = {
        PyObject_HEAD_INIT(&PyType_Type)
        0,                         /*ob_size*/
        "mytype.MyType",           /*tp_name*/
        sizeof(MyTypeObject),      /*tp_basicsize*/
        0,                         /*tp_itemsize*/
        ...
        0,                         /*tp_repr*/
        &mytype_as_number,         /*tp_as_number*/
        0,                         /*tp_as_sequence*/
        ...
        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,/*tp_flags*/
        ...
    };
    

    设置此标志后,解释器不会检查类型,因此您必须手动处理它们。

    就地,操作员总是允许不同的类型。为什么,我不知道。