Python 接受可选整数的 C 扩展函数
Python C extension function that accepts optional integer
我想在 C 扩展模块中实现以下 Python 函数:
def value(x: Optional[int] = None) -> Optional[int]:
if x is None:
# act like a getter
return APIGetValue() # retrieve the value from an external library
# act like a setter
APISetValue(x) # pass the value to an external library
return None
这是我目前得到的:
static PyObject* MyLib_PyValue(PyObject *self, PyObject *args, PyObject *kwargs) {
static char *kwlist[] = { "x", NULL };
int x;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i:value", kwlist, &x)) {
return NULL;
}
if (x == NULL) {
return PyLong_FromLong(APIGetValue());
}
APISetValue(x);
Py_RETURN_NONE;
}
使用 args 调用函数是有效的,但是当将 value()
作为 getter 调用时,我得到 1
而不是 NULL
。我该如何进行?我对 Python 的 C API 还不是很熟悉。
首先,你不能有一个 NULL int。 NULL 是指针的东西。由于 C 类型转换的工作方式以及 NULL
宏的定义方式,带有 int x
的 x == NULL
通常会执行以下两种操作之一:它的行为与 x == 0
相同,或者它会产生编译时错误。
二、引用Python C API argument parsing docs,
The C variables corresponding to optional arguments should be initialized to their default value — when an optional argument is not specified, PyArg_ParseTuple() does not touch the contents of the corresponding C variable(s).
PyArg_ParseTupleAndKeywords
也是如此。您的 x
未初始化,并且 PyArg_ParseTupleAndKeywords
未为其写入值,因此在 x == NULL
比较中访问 x
的值是未定义的行为。
您需要初始化 x
,并且您需要使用实际允许您检测缺失值的类型。这可能意味着将 x
声明为 PyObject *x = NULL;
并将 "|O:value"
传递给 PyArg_ParseTupleAndKeywords
,然后在函数内部处理到 C long 的转换,而不是依赖 PyArg_ParseTupleAndKeywords
来完成它。
这是最终结果,以防有人需要工作片段:
static PyObject* MyLib_PyValue(PyObject *self, PyObject *args, PyObject *kwargs) {
static char *kwlist[] = { "x", NULL };
PyObject *pyX = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O:value", kwlist, &pyX)) {
return NULL;
}
if (pyX == NULL) { // getter
return PyLong_FromLong(APIGetValue());
}
// setter
int x = (int)PyLong_AsLong(pyX);
if (PyErr_Occurred()) {
return NULL;
}
if (x < 0) {
PyErr_SetString(PyExc_ValueError, "x must be positive");
return NULL;
}
APISetValue(x);
Py_RETURN_NONE;
}
我想在 C 扩展模块中实现以下 Python 函数:
def value(x: Optional[int] = None) -> Optional[int]:
if x is None:
# act like a getter
return APIGetValue() # retrieve the value from an external library
# act like a setter
APISetValue(x) # pass the value to an external library
return None
这是我目前得到的:
static PyObject* MyLib_PyValue(PyObject *self, PyObject *args, PyObject *kwargs) {
static char *kwlist[] = { "x", NULL };
int x;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i:value", kwlist, &x)) {
return NULL;
}
if (x == NULL) {
return PyLong_FromLong(APIGetValue());
}
APISetValue(x);
Py_RETURN_NONE;
}
使用 args 调用函数是有效的,但是当将 value()
作为 getter 调用时,我得到 1
而不是 NULL
。我该如何进行?我对 Python 的 C API 还不是很熟悉。
首先,你不能有一个 NULL int。 NULL 是指针的东西。由于 C 类型转换的工作方式以及 NULL
宏的定义方式,带有 int x
的 x == NULL
通常会执行以下两种操作之一:它的行为与 x == 0
相同,或者它会产生编译时错误。
二、引用Python C API argument parsing docs,
The C variables corresponding to optional arguments should be initialized to their default value — when an optional argument is not specified, PyArg_ParseTuple() does not touch the contents of the corresponding C variable(s).
PyArg_ParseTupleAndKeywords
也是如此。您的 x
未初始化,并且 PyArg_ParseTupleAndKeywords
未为其写入值,因此在 x == NULL
比较中访问 x
的值是未定义的行为。
您需要初始化 x
,并且您需要使用实际允许您检测缺失值的类型。这可能意味着将 x
声明为 PyObject *x = NULL;
并将 "|O:value"
传递给 PyArg_ParseTupleAndKeywords
,然后在函数内部处理到 C long 的转换,而不是依赖 PyArg_ParseTupleAndKeywords
来完成它。
这是最终结果,以防有人需要工作片段:
static PyObject* MyLib_PyValue(PyObject *self, PyObject *args, PyObject *kwargs) {
static char *kwlist[] = { "x", NULL };
PyObject *pyX = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O:value", kwlist, &pyX)) {
return NULL;
}
if (pyX == NULL) { // getter
return PyLong_FromLong(APIGetValue());
}
// setter
int x = (int)PyLong_AsLong(pyX);
if (PyErr_Occurred()) {
return NULL;
}
if (x < 0) {
PyErr_SetString(PyExc_ValueError, "x must be positive");
return NULL;
}
APISetValue(x);
Py_RETURN_NONE;
}