Skip to content
This repository has been archived by the owner on Mar 23, 2024. It is now read-only.

Lnk2past/malen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

97 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

malen

Tests Status Static Analysis Status PyPI - Python Version PyPI - Implementation

Malen is the German word meaning to paint. This header-only library is meant for simple embedding of Python within a C++ application.

Introduction

malen is a generic embedding framework for Python. At its simplest, you write a wrapper class for a module that you plan on interfacing with. You point it to the module you wish to load, paths that need to be available to Python, and wrappers for any number of methods you wish to call. Then you can instantiate the wrapper and call away!

Motivation

This has gone through a number of iterations and improvements for a C++ class that I teach. The idea was to provide a number of simple calls to allow users to generate simple visualizations of their data directly from their programs. All of the visualization were done through Bokeh, and so a number of prebaked methods were exposed to the students. This would let them make various plot types and add a limited set of widgets and such. I opted for Bokeh because generating HTML works a good bit better than static images (e.g. through Matplotlib) on the university's servers (to which everyone connects to remotely). Upon expanding the features and capabilities, it became immediately clear that any amount of improvements would directly lead to creating a more generalized embedding frame work.

How It Works

malen works by attempting to use a set of overloads and clever variadic templates for building argument lists. Here is a simple example (this is a version of the 01_hello_world sample without the comments, for brevity):

Here is the Python module we want to wrap:

def greet(name):
    print('Welcome to the world, ' + name + '!')

And here is the C++ code to embed it:

#include "malen/malen.h"
#include <string>

class PyHelloWorld : public malen::Malen
{
public:
    PyHelloWorld():
        malen::Malen({
            {
                "hello_world",
                {
                    "greet"
                }
            }},
            {"."})
    {}

    void greet(const std::string &name)
    {
        invoke(__FUNCTION__, malen::args(name));
    }
};

int main()
{
    PyHelloWorld phw;
    phw.greet("Lnk2past");
}

Note that out PyHelloWorld class only defines a constructor and a method (to wrap around the single method provided by hello_world.py). All of the state is held inside of the inherited malen::Malen class. The state refers to a handle to the module and any methods we end up calling. Methods can be preloaded, as they are in this example, or can be loaded upon being called (and then their handles are cached).

malen::Malen has a few constructors that we can use to define our wrapper(s). You can give it modules, modules + methods, and/or additional paths to search. In our example above we pass a mapping instructing it to load the hello_world module and from it load the greet method. We also tell it to include the current directory in the search path (as that is where we assume our module is in this example). You can use malen::Malen::load_python_method to load methods or malen::Malen::add_to_path to directory(s) to the search path whenever you want, and can even pass a module to invoke to load a method is if it not currently cached.

The malen::Malen::invoke method takes in a method name and two pointers to the functions args and kwargs. malen provides a handful of utility functions for constructing these pointers for you. Both functions malen::args and malen::kwargs are variadic templates, and handle the construction of the tuple and dictionary to be given to Python. For now you can pass simple types and some STL containers. As mentioned in the previous paragraph, you may also supply a module name in the case you may expect the method to not be laoded yet.

The kwargs default to nullptr and therefore can be omitted, but the args must always be supplied. If you do not have any arguments to pass along, just pass nullptr and malen will handle the rest.

Technically you could just instantiate a malen::Malen directly and write code/functions to work with the malen::Malen instance and whatever methods you want to call; in the end the effect is all the same and you can do what you want or need (but I prefer writing wrapper classes).

Examples & Usage

See the various examples found here. More will be added!