Minimalistic library for embedding CPython 3.x scripting language into C++ application
Lightweight simple and clean alternative to heavy boost.python. No additional dependencies. Crossplatform -- Linux, Windows platforms are supported.
cppy3 is sutable for embedding Python in C++ application while boost.python is evolved around extending Python with C++ module and it's embedding capabilities are somehow limited for now.
- Inject variables from C++ code into Python
- Extract variables from Python to C++ layer
- Reference-counted smart pointer wrapper for PyObject*
- Manage Python init/shutdown with 1 line of code
- Manage GIL with scoped lock/unlock guards
- Forward exceptions (throw in Python, catch in C++ layer)
- Nice C++ abstractions for Python native types list, dict and numpy.ndarray
- Support Numpy ndarray via tiny C++ wrappers
- Example interactive python console in 10 lines of code
- Tested on Debian Linux x64 G++ and Mac OSX M1 Clang
Features examples code snippets from tests.cpp
// create interpreter
cppy3::PythonVM instance;
// inject
cppy3::Main().injectVar<int>("a", 2);
cppy3::Main().injectVar<int>("b", 2);
cppy3::exec("assert a + b == 4");
cppy3::exec("print('sum is', a + b)");
// extract
const cppy3::Var sum = cppy3::eval("a + b");
assert(sum.type() == cppy3::Var::LONG);
assert(sum.toLong() == 4);
assert(sum.toString() == L"4");
// create interpreter
cppy3::PythonVM instance;
try {
// throw excepton in python
cppy3::exec("raise Exception('test-exception')");
assert(false && "not supposed to be here");
} catch (const cppy3::PythonException& e) {
// catch in c++
assert(e.info.type == L"<class 'Exception'>");
assert(e.info.reason == L"test-exception");
assert(e.info.trace.size() > 0);
assert(std::string(e.what()).size() > 0);
}
// create interpreter
cppy3::PythonVM instance;
cppy3::importNumpy();
// create numpy ndarray in C
double cData[2] = {3.14, 42};
// create copy
cppy3::NDArray<double> a(cData, 2, 1);
// wrap cData without copying
cppy3::NDArray<double> b;
b.wrap(data, 2, 1);
REQUIRE(a(1, 0) == cData[1]);
REQUIRE(b(1, 0) == cData[1]);
// inject into python __main__ namespace
cppy3::Main().inject("a", a);
cppy3::Main().inject("b", b);
cppy3::exec("import numpy");
cppy3::exec("assert numpy.all(a == b), 'expect cData'");
// modify b from python (b is a shared ndarray over cData)
cppy3::exec("b[0] = 100500");
assert(b(0, 0) == 100500);
assert(cData[0] == 100500);
// initially Python GIL is locked
assert(cppy3::GILLocker::isLocked());
// add variable
cppy3::exec("a = []");
cppy3::List a = cppy3::List(cppy3::lookupObject(cppy3::getMainModule(), L"a"));
assert(a.size() == 0);
// create thread that changes the variable a in a different thread
const std::string threadScript = R"(
import threading
def thread_main():
global a
a.append(42)
t = threading.Thread(target=thread_main, daemon=True)
t.start()
)";
std::cout << threadScript << std::endl;
cppy3::exec(threadScript);
{
// release GIL on this thread
cppy3::ScopedGILRelease gilRelease;
assert(!cppy3::GILLocker::isLocked());
// and wait thread changes the variable
sleep(0.1F);
{
// lock GIL again before accessing python objects
cppy3::GILLocker locker;
assert(cppy3::GILLocker::isLocked());
// ensure that variable has been changed
cppy3::exec("assert a == [42], a");
assert(a.size() == 1);
assert((a[0]).toLong() == 42);
}
// GIL is released again
assert(!cppy3::GILLocker::isLocked());
}
- C++11 compatible compiler
- CMake 3.12+
- python3 dev package (with numpy recommended)
Brew python package has altogether dev headers and numpy included
sudo brew install cmake python3
sudo apt-get install cmake g++ python3-dev
Numpy is very much desired but optional
sudo apt-get install python3-numpy
Cmake, Python with numpy is recommended.
mkdir build
cd build && cmake ..
make
./tests/tests
mkdir build
cd build && cmake ..
make
./console
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
MIT License. Feel free to use