First steps#
Generate bindings for functions#
Using litgen is straightforward. The simplest use case is:
import litgen
instantiate some options
convert some code into C++ binding code and python declarations (stubs)
use the generated code
Below we import litgen, define the C++ code we want to bind, and run litgen.generate_code
# Import litgen
import litgen
# Instantiate some options
options = litgen.LitgenOptions()
# Code for which we will emit bindings
cpp_code = """
// Mathematic operations
// Adds two integers
int Add(int x, int y = 2);
int Sub(int x, int y = 2); // Substract two integers
"""
# Run the generator
generated_code = litgen.generate_code(options, cpp_code)
The generated code contains several elements:
stub_code
contains the python declarations, including all comments. They enable to have a flawless code navigation and completion inside IDEs.pydef_code
contains the C++ binding code. This code is specific to pybind11glue_code
: optional additional C++ code that is emitted in speficic advanced cases (more details later in this manual). Most of the time, it will be empty
Let’s see the python declarations corresponding to the C++ code: they should be copied into a python stub file (*.pyi) that describes the bindings offered by your library.
Note: below, we import
litgen_demo
to be able to display code in this page. You will not need it: you can simply callprint(generated_code.stub_code)
from litgen.demo import litgen_demo
litgen_demo.demo(options, cpp_code, show_pydef=True)
# Below, you will see:
# - the original C++ code to the left
# - stub_code (i.e. the python stubs) to the right
# - pydef_code (i.e. the C++ binding code) at the bottom
# In this case, the glue code is empty
// Mathematic operations
// Adds two integers
int Add(int x, int y = 2);
int Sub(int x, int y = 2); // Substract two integers
# Mathematic operations
def add(x: int, y: int = 2) -> int:
""" Adds two integers"""
pass
def sub(x: int, y: int = 2) -> int:
""" Substract two integers"""
pass
m.def("add",
Add,
py::arg("x"), py::arg("y") = 2,
"Adds two integers");
m.def("sub",
Sub,
py::arg("x"), py::arg("y") = 2,
"Substract two integers");
m.def("add",
Add,
nb::arg("x"), nb::arg("y") = 2,
"Adds two integers");
m.def("sub",
Sub,
nb::arg("x"), nb::arg("y") = 2,
"Substract two integers");
Binding a simple class or struct#
For a simple class, public members and methods will be exposed. For a simple struct, a constructor with named parameters will be exposed in Python.
options = litgen.LitgenOptions()
cpp_code = """
class FooClass {
private:
int mPriv = 0;
public:
FooClass(int v);
int mPublic = 1;
void ShowInfo();
};
struct FooStruc {
int a = 1;
void ShowInfo();
};
"""
litgen_demo.demo(options, cpp_code, show_pydef=True)
class FooClass {
private:
int mPriv = 0;
public:
FooClass(int v);
int mPublic = 1;
void ShowInfo();
};
struct FooStruc {
int a = 1;
void ShowInfo();
};
class FooClass:
def __init__(self, v: int) -> None:
pass
m_public: int = 1
def show_info(self) -> None:
pass
class FooStruc:
a: int = 1
def show_info(self) -> None:
pass
def __init__(self, a: int = 1) -> None:
"""Auto-generated default constructor with named params"""
pass
auto pyClassFooClass =
py::class_<FooClass>
(m, "FooClass", "")
.def(py::init<int>(),
py::arg("v"))
.def_readwrite("m_public", &FooClass::mPublic, "")
.def("show_info",
&FooClass::ShowInfo)
;
auto pyClassFooStruc =
py::class_<FooStruc>
(m, "FooStruc", "")
.def(py::init<>([](
int a = 1)
{
auto r = std::make_unique<FooStruc>();
r->a = a;
return r;
})
, py::arg("a") = 1
)
.def_readwrite("a", &FooStruc::a, "")
.def("show_info",
&FooStruc::ShowInfo)
;
auto pyClassFooClass =
nb::class_<FooClass>
(m, "FooClass", "")
.def(nb::init<int>(),
nb::arg("v"))
.def_rw("m_public", &FooClass::mPublic, "")
.def("show_info",
&FooClass::ShowInfo)
;
auto pyClassFooStruc =
nb::class_<FooStruc>
(m, "FooStruc", "")
.def("__init__", [](FooStruc * self, int a = 1)
{
new (self) FooStruc(); // placement new
auto r = self;
r->a = a;
},
nb::arg("a") = 1
)
.def_rw("a", &FooStruc::a, "")
.def("show_info",
&FooStruc::ShowInfo)
;
Generator options#
There are numerous generations options that can be set, and they are explained in details later in this manual: See full list
About glue code#
In some cases, some additional “glue code” will be emitted. This is the case for example when we want to bind a function that wants to modify a parameter whose type is immutable in python.
In this case, we will need to set one option (options.fn_params_replace_modifiable_immutable_by_boxed__regex
)
options = litgen.LitgenOptions()
# This a regex which specifies that we want to adapt all functions with boxed types when needed
options.fn_params_replace_modifiable_immutable_by_boxed__regex = r".*"
cpp_code = """
// changes the value of the bool parameter (passed by modifiable reference)
// (This function will use a BoxedBool in the python code, so that its value can be modified)
void SwitchBoolValue(bool &v);
"""
# Run the generator
litgen_demo.demo(options, cpp_code, show_pydef=True)
// changes the value of the bool parameter (passed by modifiable reference)
// (This function will use a BoxedBool in the python code, so that its value can be modified)
void SwitchBoolValue(bool &v);
#################### <generated_from:BoxedTypes> ####################
class BoxedBool:
value: bool
def __init__(self, v: bool = False) -> None:
pass
def __repr__(self) -> str:
pass
#################### </generated_from:BoxedTypes> ####################
def switch_bool_value(v: BoxedBool) -> None:
""" changes the value of the bool parameter (passed by modifiable reference)
(This function will use a BoxedBool in the python code, so that its value can be modified)
"""
pass
//////////////////// <generated_from:BoxedTypes> ////////////////////
auto pyClassBoxedBool =
py::class_<BoxedBool>
(m, "BoxedBool", "")
.def_readwrite("value", &BoxedBool::value, "")
.def(py::init<bool>(),
py::arg("v") = false)
.def("__repr__",
&BoxedBool::__repr__)
;
//////////////////// </generated_from:BoxedTypes> ////////////////////
m.def("switch_bool_value",
[](BoxedBool & v)
{
auto SwitchBoolValue_adapt_modifiable_immutable = [](BoxedBool & v)
{
bool & v_boxed_value = v.value;
SwitchBoolValue(v_boxed_value);
};
SwitchBoolValue_adapt_modifiable_immutable(v);
},
py::arg("v"),
" changes the value of the bool parameter (passed by modifiable reference)\n (This function will use a BoxedBool in the python code, so that its value can be modified)");
//////////////////// <generated_from:BoxedTypes> ////////////////////
auto pyClassBoxedBool =
nb::class_<BoxedBool>
(m, "BoxedBool", "")
.def_rw("value", &BoxedBool::value, "")
.def(nb::init<bool>(),
nb::arg("v") = false)
.def("__repr__",
&BoxedBool::__repr__)
;
//////////////////// </generated_from:BoxedTypes> ////////////////////
m.def("switch_bool_value",
[](BoxedBool & v)
{
auto SwitchBoolValue_adapt_modifiable_immutable = [](BoxedBool & v)
{
bool & v_boxed_value = v.value;
SwitchBoolValue(v_boxed_value);
};
SwitchBoolValue_adapt_modifiable_immutable(v);
},
nb::arg("v"),
" changes the value of the bool parameter (passed by modifiable reference)\n (This function will use a BoxedBool in the python code, so that its value can be modified)");
struct BoxedBool
{
bool value;
BoxedBool(bool v = false) : value(v) {}
std::string __repr__() const { return std::string("BoxedBool(") + std::to_string(value) + ")"; }
};