使用Python C Api部分解析args/kwargs

2024-10-04 13:20:57 发布

您现在位置:Python中文网/ 问答频道 /正文

我尝试使用pythoncapi实现一个Python函数,该函数的功能类似于(此实现被简化,并且不包括键的NULL检查):

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


Tags: to函数inifargskwargspyobjectthrow
1条回答
网友
1楼 · 发布于 2024-10-04 13:20:57

不能直接使用PyArg_ParseTupleAndKeywords,因为它需要设置所有可能的关键字参数。它没有“任何剩余参数都是关键字”模式

使用它的一个选项是将arg1arg2复制/移动到另一个dict并将其馈送到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处理

相关问题 更多 >