如何在Python C-API中获取当前函数名?

4 投票
4 回答
2998 浏览
提问于 2025-04-16 20:45

我实现了一些函数,这些函数都是通过一个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 个回答

2

注意:这里没有提供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 *。不过,现在只需要把它改成一些有趣的东西就可以了。

4

我一直在尝试解决一个非常相似的问题。

我得出的结论是,在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的回答。

2

Python的接口并不会告诉你它正在调用哪个函数。你已经创建了一个函数,接口会调用它,假设你知道自己写了什么函数。你需要为每个Python函数创建一个小的包装函数。最好的方法是把你的一个C函数注册为一个Python函数,并让它的第一个参数是一个字符串。然后,在你的Python代码中,根据需要创建多个Python函数,每个函数都用正确的字符串参数来调用你的C函数,这样就能明确你想调用哪个函数。

另一种选择是重新考虑你的C代码结构,创建尽可能多的函数,每个函数都调用你的公共辅助代码来处理选项等。

撰写回答