为什么 PyLong_AsUnsignedLongLong 函数无法转换 numpy.uint64 元素,而 PyLong_AsLongLong 成功?

Why does PyLong_AsUnsignedLongLong function fail to convert a numpy.uint64 element, whereas PyLong_AsLongLong succeeds?

我正在为 Python 开发一个 C 扩展,它实现了一个方法来转换 numpy 元素列表(在本例中为 numpy.uint64 ) 到 unsigned long long C 类型 (使用 PyLong_AsUnsignedLongLong 函数)。然后对列表的元素求和,并将所得总和返回到 Python 层。

为了创建模块,我在 testmodule.c:

中编写了这段代码
#include <Python.h>

static PyObject *method_sum_list_u64(PyObject *self, PyObject *args);

static PyMethodDef testmoduleMethods[] = {
    {"sum_list_u64", method_sum_list_u64, METH_VARARGS, "docs"},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef testmodule = {
    PyModuleDef_HEAD_INIT,
    "testmodule",
    "docs",
    -1,
    testmoduleMethods
};

PyMODINIT_FUNC PyInit_testmodule(void) {
    return PyModule_Create(&testmodule);
}

这是我的方法:

static PyObject *method_print_list_u64(PyObject *self, PyObject *args) {
    uint64_t sum = 0;
    PyObject *input_list;

    if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &input_list))
    {
        return NULL;
    }

    Py_ssize_t data_points = PyList_Size(input_list);

    for (Py_ssize_t i = 0; i < data_points; i++)
    {
        PyObject *item = PyList_GetItem(input_list, i);
        sum += PyLong_AsUnsignedLongLong(item);
    }

    return PyLong_FromUnsignedLongLong(sum);
}

我的 setup.py 文件:

from setuptools import setup, Extension

def main():
    setup(name="testmodule",
          version="1.0.1",
          description="Python interface for the testmodule C library function",
          ext_modules=[Extension("testmodule", ["testmodule.c"])])

if __name__ == "__main__":
    main()

还有一个名为 mytest.py 的简单测试脚本:

import numpy as np
import testmodule

input_list = [np.uint64(1000)]
print(testmodule.sum_list_u64(input_list))

重现我的错误 运行:

$ python setup.py install
$ python mytest.py
TypeError: an integer is required

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "mytest.py", line 5, in <module>
    print(testmodule.sum_list_u64(input_list))
SystemError: <built-in function sum_list_u64> returned a result with an error set

现在,如果我用 PyLong_AsLongLong 替换 PyLong_AsUnsignedLongLong 一切正常。为什么 PyLong_AsUnsignedLongLong 失败而 PyLong_AsLongLong 没有?

isinstance(numpy.uint64(5),int) returns False - Numpy 标量整数类型不是 Python 整数类型的子类。它们具有不同的内部结构,因此不能与期望 Python 整数的 C API 函数一起使用(值得注意的是,Python 整数类型可以处理任意大的整数,而 Numpy 类型是有限的到内部使用的 C 类型的大小)。

如果您阅读 the documentation,您会看到 PyLong_AsLongLong 将尝试转换为 Python int,而 PyLong_AsUnsignedLongLong 将仅接受 [= 的实例实例14=]。我不知道为什么会这样,我认为这不会很容易找出来。但是,这是记录在案的行为,它解释了您所看到的内容。

几个选项:

  • 首先将所有内容转换为Python PyLongObject。这个方法可能是最健壮的(它会接受你传递给它的任何合理的数据类型),但确实涉及创建中间 Python 对象(当你完成它们时记得 DECREF 它们) .使用 PyObject_CallFunctionObjArgs(PyLongObject, item, NULL)(尽管其他选项也可用)。然后你可以使用 PyLong_AsUnsignedLongLong 因为你现在肯定有正确的类型。

  • 如果您总是传递 Numpy 标量类型,则使用 Numpy 函数 PyArray_ScalarAsCtype 快速直接地从中获取数据。您 需要一些错误和类型检查以确保您确实收到了 numpy.uint64.

  • 考虑传递一个 Numpy 数组而不是 numpy.uint64 的列表。使用孤立的 Numpy 标量类型并不清楚你得到了什么,而数组在内部存储为 uint64_t 的 C 数组,你可以快速迭代。

  • 考虑删除 numpy.uint64s 并在列表中只使用普通的 Python ints 因为我看不到你使用 Numpy 有什么好处类型。然后你可以调用 PyLong_AsUnsignedLongLong.


您的函数中还缺少 所有 错误检查。大多数 C API 调用都有一个 return 值表示错误(通常是 NULL 但有时不同)。检查这一点很重要。不要跳过它!