Skip to content

reefactor/cppy3

Folders and files

NameName
Last commit message
Last commit date

Latest commit

501a49a · Sep 5, 2024

History

56 Commits
Sep 5, 2024
Dec 5, 2023
Sep 2, 2024
Aug 30, 2024
Sep 2, 2024
Apr 27, 2024
Sep 2, 2024
Sep 24, 2018
May 2, 2024
Aug 30, 2024

Repository files navigation

cppy3

Embed Python 3 into your C++ app in 10 minutes

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.

Features

  • 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

Inject/extract variables C++ -> Python -> C++
// 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");
Forward exceptions Python -> C++
// 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);

}

Support numpy ndarray

// 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);

Scoped GIL Lock / Release management

// 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());
}

Requirements

  • C++11 compatible compiler
  • CMake 3.12+
  • python3 dev package (with numpy recommended)

Build

Prerequisites
MacOSX

Brew python package has altogether dev headers and numpy included

sudo brew install cmake python3
Linux (Debian)
sudo apt-get install cmake g++ python3-dev

Numpy is very much desired but optional

sudo apt-get install python3-numpy
Windows

Cmake, Python with numpy is recommended.

Build

Testdrive

mkdir build
cd build && cmake ..
make
./tests/tests

Example interactive python console

mkdir build
cd build && cmake ..
make
./console

Release build

mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .

License

MIT License. Feel free to use