diff --git a/include/pybind11/eval.h b/include/pybind11/eval.h index 05d7bb614..fa6b8af47 100644 --- a/include/pybind11/eval.h +++ b/include/pybind11/eval.h @@ -14,6 +14,22 @@ #include "pybind11.h" PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +inline void ensure_builtins_in_globals(object &global) { + #if PY_VERSION_HEX < 0x03080000 + // Running exec and eval on Python 2 and 3 adds `builtins` module under + // `__builtins__` key to globals if not yet present. + // Python 3.8 made PyRun_String behave similarly. Let's also do that for + // older versions, for consistency. + if (!global.contains("__builtins__")) + global["__builtins__"] = module_::import(PYBIND11_BUILTINS_MODULE); + #else + (void) global; + #endif +} + +PYBIND11_NAMESPACE_END(detail) enum eval_mode { /// Evaluate a string containing an isolated expression @@ -31,6 +47,8 @@ object eval(str expr, object global = globals(), object local = object()) { if (!local) local = global; + detail::ensure_builtins_in_globals(global); + /* PyRun_String does not accept a PyObject / encoding specifier, this seems to be the only alternative */ std::string buffer = "# -*- coding: utf-8 -*-\n" + (std::string) expr; @@ -85,6 +103,8 @@ object eval_file(str fname, object global = globals(), object local = object()) if (!local) local = global; + detail::ensure_builtins_in_globals(global); + int start; switch (mode) { case eval_expr: start = Py_eval_input; break; diff --git a/tests/test_eval.cpp b/tests/test_eval.cpp index 03d0fb366..5416c2ec4 100644 --- a/tests/test_eval.cpp +++ b/tests/test_eval.cpp @@ -88,4 +88,12 @@ TEST_SUBMODULE(eval_, m) { } return false; }); + + // test_eval_empty_globals + m.def("eval_empty_globals", [](py::object global) { + if (global.is_none()) + global = py::dict(); + auto int_class = py::eval("isinstance(42, int)", global); + return global; + }); } diff --git a/tests/test_eval.py b/tests/test_eval.py index b6f9d1881..1bb05af05 100644 --- a/tests/test_eval.py +++ b/tests/test_eval.py @@ -25,3 +25,11 @@ def test_eval_file(): assert m.test_eval_file(filename) assert m.test_eval_file_failure() + + +def test_eval_empty_globals(): + assert "__builtins__" in m.eval_empty_globals(None) + + g = {} + assert "__builtins__" in m.eval_empty_globals(g) + assert "__builtins__" in g