如何在Python C-API中获取当前函数名?
我实现了一些函数,这些函数都是通过一个C语言的函数被Python解释器调用的:
PyObject *
CmdDispatch(PyObject *self, PyObject *args, PyObject *kwargs)
意外的是,self的值是NULL,我需要获取当前被调用的函数名称。有没有办法获取这个信息呢?
我有很多函数都在执行这个过程。这个命令会把所有选项处理成一个C++的映射表,然后把它传递给每个命令的实现。
更新: http://docs.python.org/extending/extending.html#a-simple-example上特别提到“self参数指向模块级函数的模块对象;对于方法来说,它指向对象实例。”但是在链接Python 2.6时,我得到的是NULL。
4 个回答
注意:这里没有提供API的错误检查,以便更清楚地说明;
这个例子直接在Python的__builtin__模块上插入了一个新函数,这样你就可以不需要使用类.method的方式来调用这个方法。你只需把mymodule换成你想要的其他模块即可。
PyObject* py_cb(PyObject *self, PyObject *args)
{
const char *name = (const char *) PyCObject_AsVoidPtr(self);
printf("%s\n", name);
Py_RETURN_NONE;
}
int main(int argc, char *argv)
{
PyObject *mod, *modname, *dict, *fnc, *usrptr;
const char *mymodule = "__builtin__";
PyMethodDef *m;
const char *method = "hello_python";
Py_Initialize();
mod = PyImport_ImportModule(mymodule);
modname = PyString_FromString(mymodule);
dict = PyModule_GetDict(mod);
m = (PyMethodDef *) calloc(1, sizeof(PyMethodDef));
m->ml_name = strdup(method);
m->ml_meth = py_cb;
usrptr = PyCObject_FromVoidPtr("I'm am the hello_python!", NULL);
fnc = PyCFunction_NewEx(m, usrptr, modname);
PyDict_SetItemString(dict, method, fnc);
...
当Python脚本执行hello_python时,py_cb扩展函数会显示:
我是hello_python!
这里的self用于发送一个真实的指针,比如库的上下文,而不是这个例子中的const char *。不过,现在只需要把它改成一些有趣的东西就可以了。
我一直在尝试解决一个非常相似的问题。
我得出的结论是,在Python的C API层面上,无法确定当前函数或调用者的名称。原因是,Python解释器只会在调用栈上放置纯Python函数(也就是用Python自己实现的函数)。而用C语言实现的函数,无论它们是否在模块方法表中注册,都不会出现在Python的调用栈上,因此通过检查栈帧是无法找到它们的。
下面是一个简单的Python示例,展示了我想要实现的功能(我猜Juan想要的也是类似的效果):
import sys
def foo():
print('foo:', sys._getframe(0).f_code.co_name)
def bar():
print('bar:', sys._getframe(0).f_code.co_name)
foo()
bar()
这里有一个与这个示例非常接近的等价实现(基于Python 3文档),但它是使用Python的C API实现的:
// Based on example from Python 3.2.1 documentation, 5.4. Extending Embedded Python
// http://docs.python.org/release/3.2.1/extending/embedding.html#extending-embedded-python
//
#include <Python.h>
#include <frameobject.h>
static void foo()
{
PyThreadState * ts = PyThreadState_Get();
PyFrameObject* frame = ts->frame;
while (frame != 0)
{
char const* filename = _PyUnicode_AsString(frame->f_code->co_filename);
char const* name = _PyUnicode_AsString(frame->f_code->co_name);
printf("foo: filename=%s, name=%s\n", filename, name);
frame = frame->f_back;
}
}
static void bar()
{
PyRun_SimpleString(
"import sys\n"
"print(\"bar: filename=%s, name=%s\" % (sys._getframe(0).f_code.co_filename, sys._getframe(0).f_code.co_name))"
);
}
static PyObject* emb_numargs(PyObject *self, PyObject *args)
{
foo();
bar();
PyRun_SimpleString(
"import sys\n"
"print(\"emb_numargs: filename=%s, name=%s\" % (sys._getframe(0).f_code.co_filename, sys._getframe(0).f_code.co_name))"
);
return PyLong_FromLong(0);
}
static PyMethodDef EmbMethods[] = {
{"numargs", emb_numargs, METH_VARARGS, "Return number 0"},
{NULL, NULL, 0, NULL}
};
static PyModuleDef EmbModule = {
PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,
NULL, NULL, NULL, NULL
};
static PyObject* PyInit_emb(void)
{
return PyModule_Create(&EmbModule);
}
int main(int argc, char* argv[])
{
PyImport_AppendInittab("emb", &PyInit_emb);
Py_Initialize();
PyRun_SimpleString(
"import emb\n"
"print('This is Zero: ', emb.numargs())\n"
);
Py_Finalize();
return 0;
}
我希望这也能补充Ned的回答。
Python的接口并不会告诉你它正在调用哪个函数。你已经创建了一个函数,接口会调用它,假设你知道自己写了什么函数。你需要为每个Python函数创建一个小的包装函数。最好的方法是把你的一个C函数注册为一个Python函数,并让它的第一个参数是一个字符串。然后,在你的Python代码中,根据需要创建多个Python函数,每个函数都用正确的字符串参数来调用你的C函数,这样就能明确你想调用哪个函数。
另一种选择是重新考虑你的C代码结构,创建尽可能多的函数,每个函数都调用你的公共辅助代码来处理选项等。