Python C API,覆盖 PyNumberMethods nb_add 字段也更改 tp_base 中的字段

Python C API, override the PyNumberMethods nb_add field also changes the field in tp_base

我开发了一个新类型:PyAlignArray_Type 派生自 PyArray_Type

PyAlignArray_Type.tp_base = &PyArray_Type;

我重写 tp_as_number 的 nb_add 字段如下:

PyAlignArray_Type.tp_as_number->nb_add = (binaryfunc)ndarray_add ;

这工作正常,但该字段也针对基本类型 (PyArray_Type) 进行了更改。 我的问题是:如何在不更改基类型的情况下覆盖 PyNumberMethods 字段?

  typedef struct {
        PyArrayObject array;
        int  pitch;
        size_t size;
    } PyAlignArrayObject;

/*---------------PyAlignArray_Type------------------*/
static PyTypeObject PyAlignArray_Type = {
    PyObject_HEAD_INIT(NULL)
    "plibs_8.ndarray",  /* tp_name */
    sizeof(PyAlignArrayObject),      /* tp_basicsize */
    0,                       /* tp_itemsize */
    (destructor)array_dealloc,/* tp_dealloc */
    0,                       /* tp_print */
    0,                       /* tp_getattr */
    0,                       /* tp_setattr */
    0,                       /* tp_reserved */
    0,                       /* tp_repr */
    0,                       /* tp_as_number */
    0,                       /* tp_as_sequence */
    0,                       /* tp_as_mapping */
    0,                       /* tp_hash */
    0,                       /* tp_call */
    0,                       /* tp_str */
    0,                       /* tp_getattro */
    0,                       /* tp_setattro */
    0,                       /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE, /* tp_flags */
    0,                       /* tp_doc */
    0,                       /* tp_traverse */
    0,                       /* tp_clear */
    0,                       /* tp_richcompare */
    0,                       /* tp_weaklistoffset */
    0,                       /* tp_iter */
    0,                       /* tp_iternext */
    ndarray_methods,         /* tp_methods */
    ndarray_members,         /* tp_members */
    0,                       /* tp_getset */
    0,                       /* tp_base */
    0,                       /* tp_dict */
    0,                       /* tp_descr_get */
    0,                       /* tp_descr_set */
    0,                       /* tp_dictoffset */
    0,                       /* tp_init */
    0,                       /* tp_alloc */
    (newfunc)ndarray_new,    /* tp_new */

};




static PyModuleDef ndarraymodule = {
    PyModuleDef_HEAD_INIT,
    "ndarray",
    "ndarray module",
    -1,
    NULL, NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC
PyInit_ndarray(void)
{
    PyObject *m;
    import_array();
    PyAlignArray_Type.tp_base = &PyArray_Type;
    if (PyType_Ready(&PyAlignArray_Type) < 0)
        return NULL;

    // __add__ overloading
    PyAlignArray_Type.tp_as_number->nb_add = (binaryfunc)ndarray_add ;

    m = PyModule_Create(&ndarraymodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&PyAlignArray_Type);
    PyModule_AddObject(m, "ndarray", (PyObject *) &PyAlignArray_Type);
    return m;
}

我认为你是对的 - 这是一种奇怪的、非直觉的行为。它是可以修复的。 The documentation says

The tp_as_number field is not inherited, but the contained fields are inherited individually.

这意味着可以像您所做的那样覆盖它们。它实际上的意思是:

The tp_as_number field is not inherited, but the contained fields are inherited individually if you provide space for them. If you don't provide space for them then the tp_as_number field is set to point at the base classes tp_as_number field.

(斜体是我添加的。请参阅 https://github.com/python/cpython/blob/3e8d6cb1892377394e4b11819c33fbac728ea9e0/Objects/typeobject.c#L5111 and https://github.com/python/cpython/blob/3e8d6cb1892377394e4b11819c33fbac728ea9e0/Objects/typeobject.c#L4757 处的代码)。这意味着,就像在您的示例中一样,您也在更改基本 class 方法。

解决这个问题的方法是创建一个零初始化 PyNumberMethods 并在您的 class

中引用它
static PyNumberMethods align_array_number_methods = { NULL };
/* You could also specify the nb_add field here and you wouldn't
have to do it in your module init */

static PyTypeObject PyAlignArray_Type = {
    /* whole bunch of code unchanged */
    &align_array_number_methods,       /* tp_as_number */
    /* whole bunch of code unchanged */
    

你留为NULL的槽位根据PyType_Ready

中的继承规则进行填充

我认为这种奇怪行为的原因是为了避免引入内存泄漏。当您调用 PyType_Ready (这是处理继承的地方)时,它不能静态分配 PyNumberMethods 因此它必须 malloc 它。因为您不知道它是否 malloced 这些字段或永远不会被释放的内存。 (这只是动态创建类型的一个问题)。