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.
It is based on Python3, but if you change the function of the Python library to call, it will work on Python2 as well.
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.
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.
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)
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.
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.
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.
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.
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
.
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
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();
}
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
.
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.
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.
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