Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get current function name in Python C-API?

I implemented a bunch of functions and they are dispatched from the same C function called by the Python interpreter:

PyObject *
CmdDispatch(PyObject *self, PyObject *args, PyObject *kwargs)

Unexpectedly, self is NULL, and I need to get the function name currently being called. Is there any way to get this information?

I have dozens of functions which are all going through this routine. This command processes all of the options into a C++ map and passes it along to the implementation of each command.

Update: http://docs.python.org/extending/extending.html#a-simple-example specifically says "The self argument points to the module object for module-level functions; for a method it would point to the object instance.", but I am getting passed null when linking against python 2.6.

like image 886
Juan Avatar asked Feb 23 '23 16:02

Juan


2 Answers

I've been trying to solve very similar problem.

The conclusion I've come to suggests there is no way to determine name of current function or caller(s) at the level of Python C API. The reason being, Python interpreter puts on call stack only pure Python functions (implemented in Python itself). Functions implemented in C, regardless if registered in module methods table, are not put on Python stack, thus it's not possible to find them inspecting the stack frames.

Here is a quick example in Python illustrating what I wanted to achieve (I assume Juan asks for similar behaviour):

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()

Here is close equivalent of this example (based on the Python 3 docs) but implemented using 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;
}

I hope this completes Ned's answer too.

like image 152
mloskot Avatar answered Mar 04 '23 21:03

mloskot


The Python api isn't built to tell you what function it is calling. You've created a function, and it is calling it, the API assumes you know what function you've written. You'll need to create a small wrapper function for each of your Python functions. The best way to do this is to register your one C function as one Python function that takes a string as its first argument. Then, in your Python layer, create as many Python functions as you need, each invoking your C function with the proper string argument identifying what function you really want to call.

Another alternative is to rethink the structure of your C code, and have as many functions as you need, each of which invokes your common helper code to process options, etc.

like image 20
Ned Batchelder Avatar answered Mar 04 '23 23:03

Ned Batchelder