The C language calls methods on Python code

  • 2020-10-31 21:54:45
  • OfStack

The problem

You want to safely perform an Python call in C and return the result to C. For example, you might want to use an Python function as a callback in the C language.

The solution

Calling Python from the C language is simple, but involves a few tricks. The following C code tells you how to make a safe call:


#include <Python.h>

/* Execute func(x,y) in the Python interpreter. The
arguments and return result of the function must be Python floats */
double call_func(PyObject *func, double x, double y) {
PyObject *args; PyObject *kwargs; PyObject *result = 0; double retval;

/* Make sure we own the GIL */ PyGILState_STATE state = PyGILState_Ensure();

/* Verify that func is a proper callable */ if (!PyCallable_Check(func)) {

fprintf(stderr, " call_func: expected a callablen " ); goto fail;
} /* Build arguments */ args = Py_BuildValue( " (dd) " , x, y); kwargs = NULL;

/* Call the function */ result = PyObject_Call(func, args, kwargs); Py_DECREF(args); Py_XDECREF(kwargs);

/* Check for Python exceptions (if any) */ if (PyErr_Occurred()) {

PyErr_Print(); goto fail;
}

/* Verify the result is a float object */ if (!PyFloat_Check(result)) {

fprintf(stderr, " call_func: callable didn't return a floatn " ); goto fail;
}

/* Create the return value */ retval = PyFloat_AsDouble(result); Py_DECREF(result);

/* Restore previous GIL state and return */ PyGILState_Release(state); return retval;

fail:
Py_XDECREF(result); PyGILState_Release(state); abort(); // Change to something more appropriate
}

To use this function, you need to get a reference to one of the existing Python calls passed in. There are many ways to do this, such as passing a callable object to an extension module or simply writing C code to extract it from an existing module.

Here is a simple example of calling a function from an embedded Python interpreter:


#include <Python.h>

/* Definition of call_func() same as above */
...

/* Load a symbol from a module */
PyObject *import_name(const char *modname, const char *symbol) {
 PyObject *u_name, *module;
 u_name = PyUnicode_FromString(modname);
 module = PyImport_Import(u_name);
 Py_DECREF(u_name);
 return PyObject_GetAttrString(module, symbol);
}

/* Simple embedding example */
int main() {
 PyObject *pow_func;
 double x;

 Py_Initialize();
 /* Get a reference to the math.pow function */
 pow_func = import_name("math","pow");

 /* Call it using our call_func() code */
 for (x = 0.0; x < 10.0; x += 0.1) {
 printf("%0.2f %0.2f\n", x, call_func(pow_func,x,2.0));
 }
 /* Done */
 Py_DECREF(pow_func);
 Py_Finalize();
 return 0;
}

To build the sample code, you need to compile C and link it to the Python interpreter. Makefile below can show you how to do this (but you'll need 1 configuration on your machine).


all::
 cc -g embed.c -I/usr/local/include/python3.3m \
  -L/usr/local/lib/python3.3/config-3.3m -lpython3.3m

Compiling and running produces output similar to the following:

[

0.00 0.00
0.10 0.01
0.20 0.04
0.30 0.09
0.40 0.16
...

]

Here's a slightly different example of an extension function that takes a callable object and other parameters and passes them to call_func() for testing:


/* Extension function for testing the C-Python callback */
PyObject *py_call_func(PyObject *self, PyObject *args) {
 PyObject *func;

 double x, y, result;
 if (!PyArg_ParseTuple(args,"Odd", &func,&x,&y)) {
 return NULL;
 }
 result = call_func(func, x, y);
 return Py_BuildValue("d", result);
}

To use this extension function, you test it as follows:


>>> import sample
>>> def add(x,y):
...   return x+y
...
>>> sample.call_func(add,3,4)
7.0
>>>

discuss

If you call Python in C, the most important thing to remember is that the C language will be the body. That is, the C language is responsible for constructing parameters, calling Python functions, checking for exceptions, checking for types, extracting return values, and so on.

As a first step, you must first have a callable object representing the Python object you will be calling. This can be a function, class, method, built-in method, or any other implementation __call__() Something to operate. To ensure that it is callable, you can use it as follows PyCallable_Check() Do check:


double call_func(PyObject *func, double x, double y) {
 ...
 /* Verify that func is a proper callable */
 if (!PyCallable_Check(func)) {
 fprintf(stderr,"call_func: expected a callable\n");
 goto fail;
 }
 ...

Handling errors in the C code requires extra care. Generally, you cannot throw just one Python exception. Errors should be handled using the C code. In this case, we're going to pass control of the error to a call abort() Error handlers for. It will kill the whole program, which you should handle more elegantly in the real world (return 1 status code). The thing to keep in mind here is that C is the protagonist, so there is no corresponding operation to throw an exception. Error handling is something you have to think about when you're programming.

Calling a function is relatively simple -- you just need to use it PyObject_Call() , passes a callable object to it, a parameter tuple, and an optional keyword dictionary. To build parameter tuples or dictionaries, you can use Py_BuildValue() That is as follows:


double call_func(PyObject *func, double x, double y) {
 PyObject *args;
 PyObject *kwargs;

 ...
 /* Build arguments */
 args = Py_BuildValue("(dd)", x, y);
 kwargs = NULL;

 /* Call the function */
 result = PyObject_Call(func, args, kwargs);
 Py_DECREF(args);
 Py_XDECREF(kwargs);
 ...

If there are no keyword arguments, you can pass NULL. When you call a function, make sure you use it Py_DECREF() or Py_XDECREF() Clean up parameters. The second function is relatively safe because it allows the NULL pointer to be passed (just ignore it), which is why we use it to clean up optional keyword arguments.

After calling the Python function, you must check for exceptions. The PyErr_Occurred() function can be used to do this. Handling exceptions is a bit trickier, as it is written in C and does not have the same exception mechanism as Python. Therefore, you must set an exception status code, print the exception message, or otherwise handle it accordingly. In this case, we chose the simple one abort() To deal with. On the other hand, traditional C programmers can simply let programs crash.


...
/* Check for Python exceptions (if any) */
if (PyErr_Occurred()) {
 PyErr_Print();
 goto fail;
}
...
fail:
 PyGILState_Release(state);
 abort();

Extracting information from the return value of the call to the Python function typically involves type checking and extracting the value. To do this, you must use the functions in the Python object layer. Here we use it PyFloat_Check() and PyFloat_AsDouble() To check and extract the Python floating-point Numbers.

The last issue is the management of Python global locks. When accessing Python in C, you need to make sure that GIL is properly captured and released. Otherwise, it may cause the interpreter to return incorrect data or crash. Calling PyGILState_Ensure() and PyGILState_Release() ensures that all 1 cuts work.


double call_func(PyObject *func, double x, double y) {
 ...
 double retval;

 /* Make sure we own the GIL */
 PyGILState_STATE state = PyGILState_Ensure();
 ...
 /* Code that uses Python C API functions */
 ...
 /* Restore previous GIL state and return */
 PyGILState_Release(state);
 return retval;

fail:
 PyGILState_Release(state);
 abort();
}

Once 1 returns, PyCallable_Check() 0 You can ensure that the calling thread monopolizes the Python interpreter. Even if the C code is running on a thread unknown to another interpreter, it's fine. At this point, the C code is free to use any Python ES124en-ES125en function it wants. After a successful call, PyGILState_Release() is used to restore the interpreter to its original state.

And the thing to notice is that each one PyCallable_Check() 0 The call must be followed by 1 matching PyGILState_Release() Call - even if an error occurs. In this case, we're going to use 1 goto Statement looks like a horrible design, but we actually use it to transfer control to a plain exit block to perform the corresponding operation. in fail : Tag after the code and Python final : The purpose of the block is one sample.

If you use all of these conventions to write C code, including GIL management, exception checking, and error checking, you'll find that calling the Python interpreter from the C language is reliable -- even for complex programs that use advanced programming skills like multithreading.

That's how C calls Python code. For more information on C calls Python, please follow the other articles on this site!


Related articles: