HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Hello World using Python embedded in C++, with RAII library initialization

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
raiiembeddedwithworldpythonusinglibraryhelloinitialization

Problem

I have written a small Hello World application that uses Python's C-API. The C library requires some functions to be called for global library (de-)initialization and the objects created by the library eventually need to be released by passing the pointer to a specific function. I think this is a fairly common pattern in C libraries.

I attempted to use RAII, to get automatic cleanup, even when exceptions interfere with the planned control flow.

#include 
#include 

// helper to get function pointer for the macro
void Python_DecreaseRefCount(PyObject* p) {
    Py_DECREF(p);
}

int main() {
    // declare struct and create instance that handles global initialization and
    // deinitialization of Python API
    static struct PythonApi {
        PythonApi() { Py_Initialize(); }
        ~PythonApi() { Py_Finalize(); }
    } python_api;
    // create object
    // use smart pointer for automatic refcount decrement at scope end
    std::tr1::shared_ptr string(
        PyString_FromString("Hello World!\n"),
        &Python_DecreaseRefCount);
    // do something with object
    PyObject_Print(string.get(), stdout, Py_PRINT_RAW);
}


Compiles and runs with:

$ g++ test.cc pkg-config --cflags --libs python2 && ./a.out
Hello World!


I'm interested in all kinds of feedback, as to whether my attempt makes sense and if there is room for improvement. Are there best practices, or certain idioms that facilitate this, that I might have missed in my approach?

Solution

-
Are you sure also scoping de-initialization of Python to main (by removing static) is not an option?

As-is, you cannot use Python in a globals initializer or destructor anyway.

Having taken a look at Py_Finalize, I actually would not deinitialize it at all, at least not in a release-build, as none of the reasons that option is provided apply to you:


This function is provided for a number of reasons. An embedding application might want to restart Python without having to restart the application itself. An application that has loaded the Python interpreter from a dynamically loadable library (or DLL) might want to free all memory allocated by Python before unloading the DLL. During a hunt for memory leaks in an application a developer might want to free all memory allocated by Python before exiting from the application


Bugs and caveats: [... lots ...]

-
Next, you shouldn't be using a function-pointer, use an empty function-object instead.

Using a function-object facilitates inlining and as it's empty, reduces memory-overhead.

-
Actually, are you sure you actually need / want a std::shared_ptr?

Either because you are already irretrievably locked into using that type, or because you actually might need weak pointers somewhere?

If the answer is no, look into boost::intrusive_ptr, as that will save you an allocation every time you hand a PyObject to a smart-pointer's oversight, and reduces smartpointer-size by half, to raw-pointer-size.

// Hooks in same scope as PyObject (global scope), so ADL in intrusive_ptr finds them:
void intrusive_ptr_add_ref(PyObject* p) noexcept { Py_INCREF(p); }
void intrusive_ptr_release(PyObject* p) noexcept { Py_DECREF(p); }
// typedef for ease of use:
typedef boost::intrusive_ptr PyPtr;
// Use as:
PyPtr string(PyString_FromString("Hello World!\n"), false);

Code Snippets

// Hooks in same scope as PyObject (global scope), so ADL in intrusive_ptr finds them:
void intrusive_ptr_add_ref(PyObject* p) noexcept { Py_INCREF(p); }
void intrusive_ptr_release(PyObject* p) noexcept { Py_DECREF(p); }
// typedef for ease of use:
typedef boost::intrusive_ptr<PyObject> PyPtr;
// Use as:
PyPtr string(PyString_FromString("Hello World!\n"), false);

Context

StackExchange Code Review Q#120525, answer score: 3

Revisions (0)

No revisions yet.