使用 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 arg1arg2 到另一个字典并将其提供给 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_XDECREFparsed_args 上的一个简单循环,因此可以由共享 goto.

处理