使用 Python C Api 部分解析 args/kwargs
Partially parse args/kwargs using the Python C Api
我尝试使用 Python C API 实现一个 Python 函数,其功能类似于(此实现被简化并且不包括 NULL 检查之类的东西keywds):
def my_func(arg1, arg2 = 2, **kwargs):
if arg2 == 1:
# expect certain parameters to be in kwargs
if arg2 == 2:
# expect different parameters to be in kwargs
if arg2 == 3:
# call a Python function with the kwargs (whats in the kwargs is unknown)
PythonFunc(**kwargs)
arg1 和 arg2 可以是命名和未命名参数。
我想到的最好的是:
PyObject* my_func(PyObject* /*self*/, PyObject* args, PyObject* keywds) {
PyObject* arg1, arg2;
switch(PyTuple_Size(args)) {
case 0:
arg1 = PyDict_GetItemString(keywds, "arg1");
if (arg2) PyDict_DelItemString(keywds, "arg1");
else // throw TypeError
arg2 = PyDict_GetItemString(keywds, "arg2");
if (arg2) PyDict_DelItemString(keywds, "arg2");
break;
case 1:
arg1 = PyTuple_GET_ITEM(args, 0);
if (PyDict_GetItemString(keywds, "arg1")) // throw TypeError
arg2 = PyDict_GetItemString(keywds, "arg2");
if (arg2) PyDict_DelItemString(keywds, "arg2");
break;
case 2:
arg1 = PyTuple_GET_ITEM(args, 0);
if (PyDict_GetItemString(keywds, "arg1")) // throw TypeError
arg2 = PyTuple_GET_ITEM(args, 1);
if (PyDict_GetItemString(keywds, "arg2")) // throw TypeError
break;
default:
// throw TypeError
}
if (...) // parse remaining keywds according to requirements
else // call Python functions using the remaining keyword arguments
}
当函数有更多参数时,这会变得相当广泛。有没有更简单的方法来实现这种行为,例如使用 PyArg_ParseTupleAndKeywords
?
您不能直接使用 PyArg_ParseTupleAndKeywords
,因为它需要设置所有可能的关键字参数。它没有“任何剩余参数都是关键字”模式。
使用它的一个选项是 copy/move arg1
和 arg2
到另一个字典并将其提供给 PyArg_ParseTupleAndKeywords
:
int arg1;
int arg2=2;
char *keywords[] = {"arg1", "arg2", NULL};
PyObject* arg1arg2dict = PyDict_New(); // error check missing...
for (int i=0; keywords[i] != NULL; ++i) {
PyObject* item = PyDict_GetItemString(kwds, keywords[i]);
if (item) {
PyDict_SetItemString(arg1arg2dict, keywords[i], item);
PyDict_DelItemString(kwds, keywords[i]);
}
}
if (!PyArg_ParseTupleAndKeywords(args, arg1arg2dict, "i|i:f", keywords, &arg1, &arg2)) {
Py_DECREF(arg1arg2dict);
return NULL;
}
Py_DECREF(arg1arg2dict);
显然这有点长,但与您的 switch 语句不同,它可以很好地自动添加更多参数。
另一种选择是编写循环而不是 switch 语句。它没有什么太复杂,而且很容易扩展。在这个版本中你会避免 PyArg_ParseTupleAndKeywords
(因为你实际上已经实现了其中的一部分)
PyObject* parsed_args[] = {NULL, NULL};
char *keywords[] = {"arg1", "arg2", NULL};
int i=0;
for (; i < PyTuple_Size(args); ++i) {
if (keywords[i] == NULL) {
// run out of possible positional arguments - clean up and raise error
}
if (PyDict_GetItemString(kwds, keywords[i]) {
// clean up and raise an error
}
parsed_args[i] = PyTuple_GET_ITEM(args, i);
Py_INCREF(parsed_args[i]);
}
for (; keywords[i] != NULL; ++i) {
PyObject* item = PyDict_GetItemString(kwds, keywords[i]);
if (item) {
Py_INCREF(item);
parsed_args[i] = item;
PyDict_DelItemString(kwds, keywords[i]);
} else {
// raise error, goto cleanup
}
}
}
清理只是 Py_XDECREF
在 parsed_args
上的一个简单循环,因此可以由共享 goto
.
处理
我尝试使用 Python C API 实现一个 Python 函数,其功能类似于(此实现被简化并且不包括 NULL 检查之类的东西keywds):
def my_func(arg1, arg2 = 2, **kwargs):
if arg2 == 1:
# expect certain parameters to be in kwargs
if arg2 == 2:
# expect different parameters to be in kwargs
if arg2 == 3:
# call a Python function with the kwargs (whats in the kwargs is unknown)
PythonFunc(**kwargs)
arg1 和 arg2 可以是命名和未命名参数。
我想到的最好的是:
PyObject* my_func(PyObject* /*self*/, PyObject* args, PyObject* keywds) {
PyObject* arg1, arg2;
switch(PyTuple_Size(args)) {
case 0:
arg1 = PyDict_GetItemString(keywds, "arg1");
if (arg2) PyDict_DelItemString(keywds, "arg1");
else // throw TypeError
arg2 = PyDict_GetItemString(keywds, "arg2");
if (arg2) PyDict_DelItemString(keywds, "arg2");
break;
case 1:
arg1 = PyTuple_GET_ITEM(args, 0);
if (PyDict_GetItemString(keywds, "arg1")) // throw TypeError
arg2 = PyDict_GetItemString(keywds, "arg2");
if (arg2) PyDict_DelItemString(keywds, "arg2");
break;
case 2:
arg1 = PyTuple_GET_ITEM(args, 0);
if (PyDict_GetItemString(keywds, "arg1")) // throw TypeError
arg2 = PyTuple_GET_ITEM(args, 1);
if (PyDict_GetItemString(keywds, "arg2")) // throw TypeError
break;
default:
// throw TypeError
}
if (...) // parse remaining keywds according to requirements
else // call Python functions using the remaining keyword arguments
}
当函数有更多参数时,这会变得相当广泛。有没有更简单的方法来实现这种行为,例如使用 PyArg_ParseTupleAndKeywords
?
您不能直接使用 PyArg_ParseTupleAndKeywords
,因为它需要设置所有可能的关键字参数。它没有“任何剩余参数都是关键字”模式。
使用它的一个选项是 copy/move arg1
和 arg2
到另一个字典并将其提供给 PyArg_ParseTupleAndKeywords
:
int arg1;
int arg2=2;
char *keywords[] = {"arg1", "arg2", NULL};
PyObject* arg1arg2dict = PyDict_New(); // error check missing...
for (int i=0; keywords[i] != NULL; ++i) {
PyObject* item = PyDict_GetItemString(kwds, keywords[i]);
if (item) {
PyDict_SetItemString(arg1arg2dict, keywords[i], item);
PyDict_DelItemString(kwds, keywords[i]);
}
}
if (!PyArg_ParseTupleAndKeywords(args, arg1arg2dict, "i|i:f", keywords, &arg1, &arg2)) {
Py_DECREF(arg1arg2dict);
return NULL;
}
Py_DECREF(arg1arg2dict);
显然这有点长,但与您的 switch 语句不同,它可以很好地自动添加更多参数。
另一种选择是编写循环而不是 switch 语句。它没有什么太复杂,而且很容易扩展。在这个版本中你会避免 PyArg_ParseTupleAndKeywords
(因为你实际上已经实现了其中的一部分)
PyObject* parsed_args[] = {NULL, NULL};
char *keywords[] = {"arg1", "arg2", NULL};
int i=0;
for (; i < PyTuple_Size(args); ++i) {
if (keywords[i] == NULL) {
// run out of possible positional arguments - clean up and raise error
}
if (PyDict_GetItemString(kwds, keywords[i]) {
// clean up and raise an error
}
parsed_args[i] = PyTuple_GET_ITEM(args, i);
Py_INCREF(parsed_args[i]);
}
for (; keywords[i] != NULL; ++i) {
PyObject* item = PyDict_GetItemString(kwds, keywords[i]);
if (item) {
Py_INCREF(item);
parsed_args[i] = item;
PyDict_DelItemString(kwds, keywords[i]);
} else {
// raise error, goto cleanup
}
}
}
清理只是 Py_XDECREF
在 parsed_args
上的一个简单循环,因此可以由共享 goto
.