Add C++ interface for the Python interpreter
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bd483d3..2179e3a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -46,6 +46,7 @@
include/pybind11/descr.h
include/pybind11/options.h
include/pybind11/eigen.h
+ include/pybind11/embed.h
include/pybind11/eval.h
include/pybind11/functional.h
include/pybind11/numpy.h
diff --git a/docs/reference.rst b/docs/reference.rst
index 3d211f7..f375791 100644
--- a/docs/reference.rst
+++ b/docs/reference.rst
@@ -58,6 +58,17 @@
.. doxygengroup:: annotations
:members:
+Embedding the interpreter
+=========================
+
+.. doxygendefine:: PYBIND11_EMBEDDED_MODULE
+
+.. doxygenfunction:: initialize_interpreter
+
+.. doxygenfunction:: finalize_interpreter
+
+.. doxygenclass:: scoped_interpreter
+
Python build-in functions
=========================
diff --git a/include/pybind11/embed.h b/include/pybind11/embed.h
new file mode 100644
index 0000000..a924652
--- /dev/null
+++ b/include/pybind11/embed.h
@@ -0,0 +1,175 @@
+/*
+ pybind11/embed.h: Support for embedding the interpreter
+
+ Copyright (c) 2017 Wenzel Jakob <wenzel.jakob@epfl.ch>
+
+ All rights reserved. Use of this source code is governed by a
+ BSD-style license that can be found in the LICENSE file.
+*/
+
+#pragma once
+
+#include "pybind11.h"
+#include "eval.h"
+
+#if defined(PYPY_VERSION)
+# error Embedding the interpreter is not supported with PyPy
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+# define PYBIND11_EMBEDDED_MODULE_IMPL(name) \
+ extern "C" PyObject *pybind11_init_impl_##name() { \
+ return pybind11_init_wrapper_##name(); \
+ }
+#else
+# define PYBIND11_EMBEDDED_MODULE_IMPL(name) \
+ extern "C" void pybind11_init_impl_##name() { \
+ pybind11_init_wrapper_##name(); \
+ }
+#endif
+
+/** \rst
+ Add a new module to the table of builtins for the interpreter. Must be
+ defined in global scope. The first macro parameter is the name of the
+ module (without quotes). The second parameter is the variable which will
+ be used as the interface to add functions and classes to the module.
+
+ .. code-block:: cpp
+
+ PYBIND11_EMBEDDED_MODULE(example, m) {
+ // ... initialize functions and classes here
+ m.def("foo", []() {
+ return "Hello, World!";
+ });
+ }
+ \endrst */
+#define PYBIND11_EMBEDDED_MODULE(name, variable) \
+ static void pybind11_init_##name(pybind11::module &); \
+ static PyObject *pybind11_init_wrapper_##name() { \
+ auto m = pybind11::module(#name); \
+ try { \
+ pybind11_init_##name(m); \
+ return m.ptr(); \
+ } catch (pybind11::error_already_set &e) { \
+ e.clear(); \
+ PyErr_SetString(PyExc_ImportError, e.what()); \
+ return nullptr; \
+ } catch (const std::exception &e) { \
+ PyErr_SetString(PyExc_ImportError, e.what()); \
+ return nullptr; \
+ } \
+ } \
+ PYBIND11_EMBEDDED_MODULE_IMPL(name) \
+ pybind11::detail::embedded_module name(#name, pybind11_init_impl_##name); \
+ void pybind11_init_##name(pybind11::module &variable)
+
+
+NAMESPACE_BEGIN(pybind11)
+NAMESPACE_BEGIN(detail)
+
+/// Python 2.7/3.x compatible version of `PyImport_AppendInittab` and error checks.
+struct embedded_module {
+#if PY_MAJOR_VERSION >= 3
+ using init_t = PyObject *(*)();
+#else
+ using init_t = void (*)();
+#endif
+ embedded_module(const char *name, init_t init) {
+ if (Py_IsInitialized())
+ pybind11_fail("Can't add new modules after the interpreter has been initialized");
+
+ auto result = PyImport_AppendInittab(name, init);
+ if (result == -1)
+ pybind11_fail("Insufficient memory to add a new module");
+ }
+};
+
+NAMESPACE_END(detail)
+
+/** \rst
+ Initialize the Python interpreter. No other pybind11 or CPython API functions can be
+ called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The
+ optional parameter can be used to skip the registration of signal handlers (see the
+ Python documentation for details). Calling this function again after the interpreter
+ has already been initialized is a fatal error.
+ \endrst */
+inline void initialize_interpreter(bool init_signal_handlers = true) {
+ if (Py_IsInitialized())
+ pybind11_fail("The interpreter is already running");
+
+ Py_InitializeEx(init_signal_handlers ? 1 : 0);
+
+ // Make .py files in the working directory available by default
+ auto sys_path = reinterpret_borrow<list>(module::import("sys").attr("path"));
+ sys_path.append(".");
+}
+
+/** \rst
+ Shut down the Python interpreter. No pybind11 or CPython API functions can be called
+ after this. In addition, pybind11 objects must not outlive the interpreter:
+
+ .. code-block:: cpp
+
+ { // BAD
+ py::initialize_interpreter();
+ auto hello = py::str("Hello, World!");
+ py::finalize_interpreter();
+ } // <-- BOOM, hello's destructor is called after interpreter shutdown
+
+ { // GOOD
+ py::initialize_interpreter();
+ { // scoped
+ auto hello = py::str("Hello, World!");
+ } // <-- OK, hello is cleaned up properly
+ py::finalize_interpreter();
+ }
+
+ { // BETTER
+ py::scoped_interpreter guard{};
+ auto hello = py::str("Hello, World!");
+ }
+
+ .. warning::
+
+ Python cannot unload binary extension modules. If `initialize_interpreter` is
+ called again to restart the interpreter, the initializers of those modules will
+ be executed for a second time and they will fail. This is a known CPython issue.
+ See the Python documentation for details.
+
+ \endrst */
+inline void finalize_interpreter() { Py_Finalize(); }
+
+/** \rst
+ Scope guard version of `initialize_interpreter` and `finalize_interpreter`.
+ This a move-only guard and only a single instance can exist.
+
+ .. code-block:: cpp
+
+ #include <pybind11/embed.h>
+
+ int main() {
+ py::scoped_interpreter guard{};
+ py::print(Hello, World!);
+ } // <-- interpreter shutdown
+ \endrst */
+class scoped_interpreter {
+public:
+ scoped_interpreter(bool init_signal_handlers = true) {
+ initialize_interpreter(init_signal_handlers);
+ }
+
+ scoped_interpreter(const scoped_interpreter &) = delete;
+ scoped_interpreter(scoped_interpreter &&other) noexcept { other.is_valid = false; }
+ scoped_interpreter &operator=(const scoped_interpreter &) = delete;
+ scoped_interpreter &operator=(scoped_interpreter &&) = delete;
+
+ ~scoped_interpreter() {
+ if (is_valid)
+ finalize_interpreter();
+ }
+
+private:
+ bool is_valid = true;
+};
+
+NAMESPACE_END(pybind11)
diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h
index 4954dcf..45cd8bf 100644
--- a/include/pybind11/pybind11.h
+++ b/include/pybind11/pybind11.h
@@ -798,6 +798,10 @@
}
};
+/// \ingroup python_builtins
+/// Return a dictionary representing the global symbol table, i.e. ``__main__.__dict__``.
+inline dict globals() { return module::import("__main__").attr("__dict__").cast<dict>(); }
+
NAMESPACE_BEGIN(detail)
/// Generic support for creating new Python heap types
class generic_type : public object {
diff --git a/setup.py b/setup.py
index 517818e..f1eac9c 100644
--- a/setup.py
+++ b/setup.py
@@ -21,6 +21,7 @@
'include/pybind11/complex.h',
'include/pybind11/descr.h',
'include/pybind11/eigen.h',
+ 'include/pybind11/embed.h',
'include/pybind11/eval.h',
'include/pybind11/functional.h',
'include/pybind11/numpy.h',
diff --git a/tests/test_cmake_build/embed.cpp b/tests/test_cmake_build/embed.cpp
index 44900c8..b9581d2 100644
--- a/tests/test_cmake_build/embed.cpp
+++ b/tests/test_cmake_build/embed.cpp
@@ -1,13 +1,8 @@
-#include <pybind11/pybind11.h>
-#include <pybind11/eval.h>
+#include <pybind11/embed.h>
namespace py = pybind11;
-PyObject *make_module() {
- py::module m("test_cmake_build");
-
+PYBIND11_EMBEDDED_MODULE(test_cmake_build, m) {
m.def("add", [](int i, int j) { return i + j; });
-
- return m.ptr();
}
int main(int argc, char *argv[]) {
@@ -15,16 +10,12 @@
throw std::runtime_error("Expected test.py file as the first argument");
auto test_py_file = argv[1];
- PyImport_AppendInittab("test_cmake_build", &make_module);
- Py_Initialize();
- {
- auto m = py::module::import("test_cmake_build");
- if (m.attr("add")(1, 2).cast<int>() != 3)
- throw std::runtime_error("embed.cpp failed");
+ py::scoped_interpreter guard{};
- auto globals = py::module::import("__main__").attr("__dict__");
- py::module::import("sys").attr("argv") = py::make_tuple("test.py", "embed.cpp");
- py::eval_file(test_py_file, globals);
- }
- Py_Finalize();
+ auto m = py::module::import("test_cmake_build");
+ if (m.attr("add")(1, 2).cast<int>() != 3)
+ throw std::runtime_error("embed.cpp failed");
+
+ py::module::import("sys").attr("argv") = py::make_tuple("test.py", "embed.cpp");
+ py::eval_file(test_py_file, py::globals());
}
diff --git a/tests/test_embed/catch.cpp b/tests/test_embed/catch.cpp
index f79fe17..cface48 100644
--- a/tests/test_embed/catch.cpp
+++ b/tests/test_embed/catch.cpp
@@ -1,5 +1,16 @@
-// Catch provides the `int main()` function here. This is a standalone
+// The Catch implementation is compiled here. This is a standalone
// translation unit to avoid recompiling it for every test change.
-#define CATCH_CONFIG_MAIN
+#include <pybind11/embed.h>
+
+#define CATCH_CONFIG_RUNNER
#include <catch.hpp>
+
+namespace py = pybind11;
+
+int main(int argc, const char *argv[]) {
+ py::scoped_interpreter guard{};
+ auto result = Catch::Session().run(argc, argv);
+
+ return result < 0xff ? result : 0xff;
+}
diff --git a/tests/test_embed/test_interpreter.cpp b/tests/test_embed/test_interpreter.cpp
index 97af7eb..1069cca 100644
--- a/tests/test_embed/test_interpreter.cpp
+++ b/tests/test_embed/test_interpreter.cpp
@@ -1,6 +1,4 @@
-#include <pybind11/pybind11.h>
-#include <pybind11/eval.h>
-
+#include <pybind11/embed.h>
#include <catch.hpp>
namespace py = pybind11;
@@ -24,41 +22,62 @@
int the_answer() const override { PYBIND11_OVERLOAD_PURE(int, Widget, the_answer); }
};
-PyObject *make_embedded_module() {
- py::module m("widget_module");
-
+PYBIND11_EMBEDDED_MODULE(widget_module, m) {
py::class_<Widget, PyWidget>(m, "Widget")
.def(py::init<std::string>())
.def_property_readonly("the_message", &Widget::the_message);
-
- return m.ptr();
}
-py::object import_file(const std::string &module, const std::string &path, py::object globals) {
- auto locals = py::dict("module_name"_a=module, "path"_a=path);
- py::eval<py::eval_statements>(
- "import imp\n"
- "with open(path) as file:\n"
- " new_module = imp.load_module(module_name, file, path, ('py', 'U', imp.PY_SOURCE))",
- globals, locals
- );
- return locals["new_module"];
+PYBIND11_EMBEDDED_MODULE(throw_exception, ) {
+ throw std::runtime_error("C++ Error");
+}
+
+PYBIND11_EMBEDDED_MODULE(throw_error_already_set, ) {
+ auto d = py::dict();
+ d["missing"].cast<py::object>();
}
TEST_CASE("Pass classes and data between modules defined in C++ and Python") {
- PyImport_AppendInittab("widget_module", &make_embedded_module);
- Py_Initialize();
+ auto module = py::module::import("test_interpreter");
+ REQUIRE(py::hasattr(module, "DerivedWidget"));
+
+ auto locals = py::dict("hello"_a="Hello, World!", "x"_a=5, **module.attr("__dict__"));
+ py::exec(R"(
+ widget = DerivedWidget("{} - {}".format(hello, x))
+ message = widget.the_message
+ )", py::globals(), locals);
+ REQUIRE(locals["message"].cast<std::string>() == "Hello, World! - 5");
+
+ auto py_widget = module.attr("DerivedWidget")("The question");
+ auto message = py_widget.attr("the_message");
+ REQUIRE(message.cast<std::string>() == "The question");
+
+ const auto &cpp_widget = py_widget.cast<const Widget &>();
+ REQUIRE(cpp_widget.the_answer() == 42);
+}
+
+TEST_CASE("Import error handling") {
+ REQUIRE_NOTHROW(py::module::import("widget_module"));
+ REQUIRE_THROWS_WITH(py::module::import("throw_exception"),
+ "ImportError: C++ Error");
+ REQUIRE_THROWS_WITH(py::module::import("throw_error_already_set"),
+ Catch::Contains("ImportError: KeyError"));
+}
+
+TEST_CASE("There can be only one interpreter") {
+ static_assert(std::is_move_constructible<py::scoped_interpreter>::value, "");
+ static_assert(!std::is_move_assignable<py::scoped_interpreter>::value, "");
+ static_assert(!std::is_copy_constructible<py::scoped_interpreter>::value, "");
+ static_assert(!std::is_copy_assignable<py::scoped_interpreter>::value, "");
+
+ REQUIRE_THROWS_WITH(py::initialize_interpreter(), "The interpreter is already running");
+ REQUIRE_THROWS_WITH(py::scoped_interpreter(), "The interpreter is already running");
+
+ py::finalize_interpreter();
+ REQUIRE_NOTHROW(py::scoped_interpreter());
{
- auto globals = py::module::import("__main__").attr("__dict__");
- auto module = import_file("widget", "test_interpreter.py", globals);
- REQUIRE(py::hasattr(module, "DerivedWidget"));
-
- auto py_widget = module.attr("DerivedWidget")("Hello, World!");
- auto message = py_widget.attr("the_message");
- REQUIRE(message.cast<std::string>() == "Hello, World!");
-
- const auto &cpp_widget = py_widget.cast<const Widget &>();
- REQUIRE(cpp_widget.the_answer() == 42);
+ auto pyi1 = py::scoped_interpreter();
+ auto pyi2 = std::move(pyi1);
}
- Py_Finalize();
+ py::initialize_interpreter();
}