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 (tested on GCC, Clang, MSVC)
- CMake 3.12+
- python3 3.5 dev package
- numpy 1.x or 2.x (tested on numpy 1.26.4 and 2.4.0)
Brew python package has altogether dev headers and numpy included
sudo brew install cmake python3sudo apt-get install cmake g++ python3-devNumpy is very much desired but optional depending on cmake CPPY3_LINK_NUMPY option
sudo apt-get install python3-numpyCmake, Python with numpy is recommended.
mkdir build cd build && cmake .. make ./tests/testsmkdir build cd build && cmake .. make ./consolemkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release .. cmake --build .MIT License. Feel free to use