Try embedding Python in a C ++ program with pybind11

How to use pybind11 to embed Python in a C ++ program.

The following may be helpful for how to call C ++ code from Python.

It seems that pybind11 can also call Python from C ++ code, so I tried it.

Python environment

It is based on Python3, but if you change the function of the Python library to call, it will work on Python2 as well.

setup

pybind11 is header-only, so it just copies the header. After that, include pybind11 in the source code.

#include <pybind11/pybind11.h>
#include <pybind11/eval.h>

Python.h should not be included directly. As we will see later, problems can occur in a Windows environment.

Execute Python code from C ++ code

The original code is as follows.

#include <pybind11/eval.h>
#include <pybind11/pybind11.h>

#include <iostream>

namespace py = pybind11;

int main(int argc, char** argv) {
	wchar_t* program = Py_DecodeLocale(argv[0], nullptr);
	if (program == nullptr) {
		fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
		exit(1);
	}
	Py_SetProgramName(program);
	Py_Initialize();

	try {
		auto global = py::dict(py::module::import("__main__").attr("__dict__"));
		auto local = py::dict();
		//Write the code here
	} catch (py::error_already_set& e) {
		std::cout << "Python error.\n" << e.what() << "\n";
	}

	Py_Finalize();
	PyMem_RawFree(program);

	return 0;
}

pybind11 throws a py :: error_already_set exception when the Python code fails. I try to catch the exception and display the error.

Note that the destructors for the global and local variables must be executed before Py_Finalize is called.

Simple sample

Run the code described below. https://docs.python.org/3/extending/embedding.html#very-high-level-embedding

py::eval<py::eval_single_statement>("from time import time,ctime", global, local);
py::eval("print('Today is', ctime(time()))", global, local);

Python single-line statements use py :: eval_single_statement. The expression uses py :: eval_expr. (Default)

Define variables

local["x"] = 100;
py::eval<py::eval_single_statement>("y = 200", global, local);
py::eval("print('x + y =', x + y)", global, local); // x + y = 300

Just assign the value using the variable name as the key.

Get value

py::eval<py::eval_single_statement>("z = x + y", global, local);
auto z = local["z"].cast<int>();
std::cout << "cpp: z = " << z << "\n"; // cpp: z = 300

Get the value using the variable name as the key and call cast to the type you want to convert.

Function definitions and calls

py::eval<py::eval_statements>(
	"def func_01():\n"
	"	print('func_01: call')\n",
	global, local);
auto func_01 = local["func_01"];
func_01(); // func_01: call

Get a function object using the function name as a key and call it with ʻoperator () . There is also a function called call ()`, but it seems that its use is not recommended.

Pass arguments to the function

py::eval<py::eval_statements>(
	"def func_02(a, b):\n"
	"	print('func_02: {} + {} = {}'.format(a, b, a + b))\n",
	global, local);
auto func_02 = local["func_02"];
func_02(123, 456);     // func_02: 123 + 456 = 579
func_02("abc", "efg"); // func_02: abc + efg = abcdefg

It's convenient.

Get the return value of a function

py::eval<py::eval_statements>(
	"def func_03(a, b):\n"
	"	return a + b\n",
	global, local);
auto func_03 = local["func_03"];
auto result = func_03(123, 456);
std::cout << "cpp: func_03 result "
	  << py::str(result).cast<std::string>() << "\n"; // cpp: func_03 result 579

Assuming you don't know what's in the return value, you're converting it to a string with py :: str.

Prepare the module in the program

The link above details how to create a module with pybind11 and call the C ++ code.

You can also embed modules in C ++ programs. https://docs.python.org/3/extending/extending.html#the-module-s-method-table-and-initialization-function

Preparation of initialization function

Create an initialization function without using the pybind11 macro PYBIND11_PLUGIN. The defined class is used for the sample to be linked with the C ++ program.

class Job {
public:
	std::string GetName() const { return m_name; }
	void SetName(const std::string& name) { m_name = name; }

private:
	std::string m_name;
};

class Person {
public:
	std::string GetName() const { return m_name; }
	void SetName(const std::string& name) { m_name = name; }

	std::shared_ptr<Job> GetJob() const { return m_job; }
	void SetJob(const std::shared_ptr<Job>& job) { m_job = job; }

private:
	std::string m_name;
	std::shared_ptr<Job> m_job;
};

namespace py = pybind11;

PyMODINIT_FUNC PyInit_sample() {
	py::module m("sample", "pybind11 module sample.");

	py::class_<Job, std::shared_ptr<Job>> job(m, "Job");
	job.def(py::init<>()).def_property("name", &Job::GetName, &Job::SetName);

	py::class_<Person, std::shared_ptr<Person>> person(m, "Person");
	person.def(py::init<>())
		.def_property("name", &Person::GetName, &Person::SetName)
		.def_property("job", &Person::GetJob, &Person::SetJob);

	return m.ptr();
}

Registration of initialization function

int main(int argc, char** argv) {
	wchar_t* program = Py_DecodeLocale(argv[0], nullptr);
	if (program == nullptr) {
		fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
		exit(1);
	}

	PyImport_AppendInittab("sample03", PyInit_sample03);

	Py_SetProgramName(program);
	Py_Initialize();

	py::module::import("sample03");

Make sure to call PyImport_AppendInittab before Py_Initialize.

Cooperation with C ++ programs

try {
	auto global = py::dict(py::module::import("__main__").attr("__dict__"));

	//Create a function in Python(Name=Hoge, Job=Teacher)
	py::eval<py::eval_statements>(
		"import sample\n"
		"def initialize_person(p):\n"
		"	job = sample.Job()\n"
		"	job.name = 'Teacher'\n"
		"	p.name = 'Hoge'\n"
		"	p.job = job\n",
		global);
	{
		auto person = std::make_shared<Person>();
		global["initialize_person"](person);
		std::cout << "Name : " << person->GetName() << "\n";           // Name : Hoge 
		std::cout << "Job  : " << person->GetJob()->GetName() << "\n"; // Job  : Teacher
	}

	//Change function(Name=Foo, Job=Programmer)
	py::eval<py::eval_statements>(
		"import sample\n"
		"def initialize_person(p):\n"
		"	job = sample.Job()\n"
		"	job.name = 'Programmer'\n"
		"	p.name = 'Foo'\n"
		"	p.job = job\n",
		global);
	{
		auto person = std::make_shared<Person>();
		global["initialize_person"](person);
		std::cout << "Name : " << person->GetName() << "\n";           // Name : Foo
		std::cout << "Job  : " << person->GetJob()->GetName() << "\n"; // Job  : Programmer
	}
} catch (py::error_already_set& e) {
	std::cout << "Python error.\n" << e.what() << "\n";
}

The object is prepared on the C ++ side, and the value is set on the Python side.

Windows environment notes

If you do #include <python.h> in a Windows environment, the debug version of the library will be linked during backbuild. The debug version must be added during installation. Also, in the debug version of Python, "_d" must be added to the module etc.

pybind11's <pybind11 / pybind11.h> is configured to link the release version of the library even during debug builds. Note that including <Python.h> first makes no sense.

Precautions when using CMake in a Windows environment

CMake has a FindPythonLibs that finds the Python environment. If you use this, the debug version of the library will be linked during the debug build in the Windows environment. Note that if you use pybind11 in that state, you will not be able to link.

If you use CMake, use the PythonLibsNew that comes with pybind11, or use pybind11Tools.

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/pybind11/cmake")
set(Python_ADDITIONAL_VERSIONS 3.7 3.6 3.5 3.4)
find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} REQUIRED)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/pybind11/cmake")
include(pybind11Tools)

pybind11Tools has pybind11_add_module, which makes it easy to define modules. If you use this, the extension of the created module will be .pyd.

pybind11_add_module(module_sample module_sample.cpp)

Recommended Posts

Try embedding Python in a C ++ program with pybind11
Try to make a Python module in C language
Solve ABC163 A ~ C with Python
ABC166 in Python A ~ C problem
Solve ABC168 A ~ C with Python
Solve ABC036 A ~ C in Python
Solve ABC162 A ~ C with Python
Solve ABC167 A ~ C with Python
Solve ABC158 A ~ C with Python
Solve ABC037 A ~ C in Python
When writing a program in Python
Try running python in a Django environment created with pipenv
Embed a Python interpreter into a C ++ app with pybind11 + cmake
Spiral book in Python! Python with a spiral book! (Chapter 14 ~)
Solve ABC175 A, B, C in Python
[Python] A program that creates stairs with #
Try logging in to qiita with Python
I made a payroll program in Python!
Try working with binary data in Python
Try sending a SYN packet in Python
Try drawing a simple animation in Python
Write a Caesar cipher program in Python
Read a file in Python with a relative path from the program
Try HTML scraping with a Python library
Use C ++ functions from python with pybind11
Try a functional programming pipe in Python
Pass a list by reference from Python to C ++ with pybind11
Try drawing a map with python + cartopy 0.18.0
[Python] Get the files in a folder with Python
Try to calculate a statistical problem in Python
Try to draw a life curve with python
Create a virtual environment with conda in Python
A program that removes duplicate statements in Python
Try to make a "cryptanalysis" cipher with Python
A simple Pub / Sub program note in Python
Try working with Mongo in Python on Mac
Work in a virtual environment with Python virtualenv.
Create a new page in confluence with Python
Call a Python script from Embedded Python in C ++ / C ++
Try to make a dihedral group with Python
I tried adding a Python3 module in C
Challenge AtCoder (ABC) 164 with Python! A ~ C problem
Try creating a FizzBuzz problem with a shell program
I made a Caesar cryptographic program in Python.
Try scraping with Python.
Next Python in C
Try gRPC in Python
C API in Python 3
Try 9 slices in Python
I made a prime number generation program in Python
Write a super simple molecular dynamics program in python
[Unity (C #), Python] Try running Python code in Unity using IronPython
Try to make a command standby tool with python
Receive dictionary data from a Python program in AppleScript
Playing with a user-local artificial intelligence API in Python
Try implementing associative memory with Hopfield network in Python
Make a simple Slackbot with interactive button in python
Try searching for a million character profile in Python
Make a breakpoint on the c layer with python
I want to work with a robot in python.
From buying a computer to running a program with python