在C++中获取Python函数的参数名称
获取Python函数参数名的C++实现方案
我之前刚好碰到过类似的需求——在C++里调用Python函数时需要按名传参,而且要兼容.pyc文件。不用反编译字节码,完全通过Python C API就能搞定,给你两个实用的方案:
方案一:借助Python的inspect模块(简洁易维护)
Python标准库的inspect模块本来就提供了获取函数签名的能力,我们可以在C++里直接调用它的API来拿到参数名,不用自己造轮子。步骤大概是这样:
- 先拿到你要调用的Python函数对象(不管是从
.py还是.pyc加载的模块里获取,方法都是一样的) - 导入
inspect模块,调用getfullargspec或者signature方法 - 解析返回的结果,提取参数名称列表
下面是一段示例代码:
#include <Python.h> #include <vector> #include <string> std::vector<std::string> get_python_function_args(PyObject* func) { std::vector<std::string> args; // 检查输入是否是函数对象 if (!PyFunction_Check(func)) { PyErr_SetString(PyExc_TypeError, "Expected a function object"); return args; } // 导入inspect模块 PyObject* inspect_module = PyImport_ImportModule("inspect"); if (!inspect_module) { PyErr_Print(); return args; } // 获取getfullargspec函数 PyObject* getfullargspec_func = PyObject_GetAttrString(inspect_module, "getfullargspec"); Py_DECREF(inspect_module); if (!getfullargspec_func || !PyCallable_Check(getfullargspec_func)) { PyErr_Print(); Py_XDECREF(getfullargspec_func); return args; } // 调用getfullargspec(func) PyObject* argspec = PyObject_CallFunctionObjArgs(getfullargspec_func, func, NULL); Py_DECREF(getfullargspec_func); if (!argspec) { PyErr_Print(); return args; } // 从argspec中提取args列表(第一个元素是参数名列表) PyObject* args_list = PyTuple_GetItem(argspec, 0); if (!args_list || !PyList_Check(args_list)) { PyErr_Print(); Py_DECREF(argspec); return args; } // 遍历列表,把每个参数名转成C++字符串 Py_ssize_t arg_count = PyList_Size(args_list); for (Py_ssize_t i = 0; i < arg_count; ++i) { PyObject* arg_name_obj = PyList_GetItem(args_list, i); if (arg_name_obj && PyUnicode_Check(arg_name_obj)) { const char* arg_name = PyUnicode_AsUTF8(arg_name_obj); if (arg_name) { args.push_back(arg_name); } } } Py_DECREF(argspec); return args; }
这个方法的好处是,getfullargspec会帮你处理各种复杂情况,比如默认参数、可变参数(*args/**kwargs)、关键字-only参数,返回的结果里还包含参数的默认值等信息,如果你需要的话也能一起拿到。
方案二:直接操作函数的__code__属性(更底层高效)
如果你不想依赖inspect模块,可以直接访问Python函数对象的__code__属性,里面存储了函数的字节码元数据,包括参数名。
每个Python函数都有一个__code__对象,其中:
co_argcount:普通位置参数的数量co_varnames:一个元组,包含函数内所有局部变量的名称,前co_argcount个就是参数名(如果有可变参数或关键字-only参数,需要结合co_kwonlyargcount等属性处理)
示例代码如下:
#include <Python.h> #include <vector> #include <string> std::vector<std::string> get_python_function_args_direct(PyObject* func) { std::vector<std::string> args; if (!PyFunction_Check(func)) { PyErr_SetString(PyExc_TypeError, "Expected a function object"); return args; } // 获取函数的__code__属性 PyObject* code_obj = PyObject_GetAttrString(func, "__code__"); if (!code_obj) { PyErr_Print(); return args; } // 获取普通位置参数数量 PyObject* arg_count_obj = PyObject_GetAttrString(code_obj, "co_argcount"); if (!arg_count_obj || !PyLong_Check(arg_count_obj)) { PyErr_Print(); Py_DECREF(code_obj); return args; } Py_ssize_t arg_count = PyLong_AsSsize_t(arg_count_obj); Py_DECREF(arg_count_obj); // 获取变量名称元组 PyObject* varnames_obj = PyObject_GetAttrString(code_obj, "co_varnames"); if (!varnames_obj || !PyTuple_Check(varnames_obj)) { PyErr_Print(); Py_DECREF(code_obj); return args; } Py_DECREF(code_obj); // 提取前arg_count个元素作为普通参数名 for (Py_ssize_t i = 0; i < arg_count; ++i) { PyObject* arg_name_obj = PyTuple_GetItem(varnames_obj, i); if (arg_name_obj && PyUnicode_Check(arg_name_obj)) { const char* arg_name = PyUnicode_AsUTF8(arg_name_obj); if (arg_name) { args.push_back(arg_name); } } } // 处理Python 3+的关键字-only参数 PyObject* kwonly_count_obj = PyObject_GetAttrString(varnames_obj, "co_kwonlyargcount"); if (kwonly_count_obj && PyLong_Check(kwonly_count_obj)) { Py_ssize_t kwonly_count = PyLong_AsSsize_t(kwonly_count_obj); // 从arg_count的位置开始,取kwonly_count个元素作为关键字-only参数 for (Py_ssize_t i = arg_count; i < arg_count + kwonly_count; ++i) { PyObject* arg_name_obj = PyTuple_GetItem(varnames_obj, i); if (arg_name_obj && PyUnicode_Check(arg_name_obj)) { const char* arg_name = PyUnicode_AsUTF8(arg_name_obj); if (arg_name) { args.push_back(arg_name); } } } Py_DECREF(kwonly_count_obj); } Py_DECREF(varnames_obj); return args; }
关键注意事项
- 错误处理:上面的示例里加了基本的错误检查,但实际使用时要记得在每个
PyObject*操作后检查是否为NULL,并且及时处理Python异常(比如用PyErr_Occurred()判断,PyErr_Print()打印,PyErr_Clear()清除) - 版本兼容性:
co_kwonlyargcount是Python 3才有的属性,如果你要兼容Python 2,需要做版本判断;inspect.getfullargspec在Python 3.0+可用,替代了Python 2的inspect.getargspec - 对象生命周期:所有通过
PyXXX_New、PyImport_ImportModule等获取的PyObject*,都要记得用Py_DECREF释放,避免内存泄漏
这两个方案都不需要解析.py或反编译.pyc,完全是通过Python运行时的API来获取参数名,不管函数是从.py还是.pyc加载的,都能正常工作。
内容的提问来源于stack exchange,提问作者keith969
相关产品推荐
相关产品推荐

