Implementing numeric methods always returns NotImplemented

I am writing a new type of extension, but I have a problem setting numerical operations (e.g. addition / subtraction / multiplication). I managed to set some operations in place, while normal operations are not called.

For example, I have a function:

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

And I asked it in number methods like this:

 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 */ ... }; 

Now, when I try to use my type, I get this behavior:

 >>> 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' 

But if I use the in-place operator:

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

If I use dir() for an object, I can see the __mul__ and __rmul__ (which means that python sees them), but it looks like they are not being called at all. Using a.__mul__(2) returns NotImplemented .

also:

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

so, as you can see, this is exactly the same thing.

What's happening? Why does the same exact function work for the operator in place, but not for the "normal" operator? I also thought that I could use the wrong slot, but I checked twice, and it is correct, as well as setting it to nb_add , nb_sub , etc. Does not work.

+4
source share
1 answer

Thanks to nneonneo's comments, I realized what was wrong. Basically, I forgot to set the Py_TPFLAGS_CHECKTYPES flag.

There are some clues about this lack in the description I gave:

  • Methods are one and the same object, but it acts differently for operations on the spot
  • In the comments, I also said that, for example, executing a*a would give the correct result, but a*different-type would not.

This clearly means that the interpreter, when performing operations out of place, checks the types of arguments and calls my function if the type is MyType , otherwise it returns NotImplemented .

Finding bits in the documentation is pretty simple to see that this is the default behavior for numeric methods.

If the type of the argument does not belong to the same class, it is assumed that the operation is not implemented.

To allow sharing between different types, you must set the Py_TPFLAGS_CHECKTYPES flag in 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*/ ... }; 

When this flag is set, the interpreter does not check types and therefore you have to deal with them manually.

Instead, in-place operators always allow different types. Why i do not know.

+3
source

Source: https://habr.com/ru/post/1434465/


All Articles