【问题标题】:How to get current function name in Python C-API?如何在 Python C-API 中获取当前函数名?
【发布时间】:2011-07-03 21:50:36
【问题描述】:

我实现了一堆函数,它们是从 Python 解释器调用的同一个 C 函数中分派的:

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。

【问题讨论】:

  • 为什么说“意外”?
  • 因为 api 文档没有说第一个参数没有任何意义
  • 有很多 API 文档;你需要更具体。
  • 根据我的研究,我不同意。它们是非特定的并且缺乏
  • 如果您使用的是 2.6,您应该查看 2.6 文档(docs.python.org/release/2.6.7/c-api/structures.html),其中说“第一个是方法的 self 对象;对于模块函数,它具有赋予 Py_InitModule4 的值()(如果使用了 Py_InitModule(),则为 NULL)。"

标签: python python-c-api


【解决方案1】:

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

我得出的结论表明,在 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 docs),但使用 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 api 并不是为了告诉您它正在调用什么函数而构建的。你已经创建了一个函数,它正在调用它,API 假设你知道你写了什么函数。您需要为每个 Python 函数创建一个小型包装函数。最好的方法是将您的一个 C 函数注册为一个 Python 函数,该函数将字符串作为其第一个参数。然后,在您的 Python 层中,根据需要创建尽可能多的 Python 函数,每个函数都使用正确的字符串参数调用您的 C 函数,以标识您真正想要调用的函数。

    另一种选择是重新考虑 C 代码的结构,并根据需要拥有尽可能多的函数,每个函数都会调用您的常用帮助代码来处理选项等。

    【讨论】:

    • 网上有关于使用脚本级别的自省来获取函数名称的文档,但不是在 c-api 级别
    • 我最终遍历了模块中的所有函数,并为每个传递命令名称的函数创建了一个包装器。
    【解决方案3】:

    为清楚起见,未提供 API 错误检查;

    本例直接在python __builtin__ 模块上插入一个新函数,允许在没有class.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】:

      我不知道是否可以直接从 C-API 完成。在最坏的情况下,您可以从 C 中调用 traceback 模块,以获取调用者的名称。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-02-28
        • 2011-09-29
        • 2020-01-11
        • 2019-03-04
        • 2014-01-15
        • 1970-01-01
        • 1970-01-01
        • 2016-01-14
        相关资源
        最近更新 更多