From c6ad2c4993fecb60503b6f2ab9941f4ccae5fe5c Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 9 Jun 2016 16:10:26 +0200 Subject: [PATCH] added exec functions --- docs/advanced.rst | 30 ++++++++ docs/reference.rst | 23 ++++++ example/CMakeLists.txt | 2 + example/example.cpp | 2 + example/example18.cpp | 120 +++++++++++++++++++++++++++++ example/example18.py | 5 ++ example/example18.ref | 15 ++++ example/example18_call.py | 1 + include/pybind11/exec.h | 156 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 354 insertions(+) create mode 100644 example/example18.cpp create mode 100644 example/example18.py create mode 100644 example/example18.ref create mode 100644 example/example18_call.py create mode 100644 include/pybind11/exec.h diff --git a/docs/advanced.rst b/docs/advanced.rst index c257fa2d4..a13bc2b6f 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -1611,3 +1611,33 @@ work, it is important that all lines are indented consistently, i.e.: .. [#f4] http://www.sphinx-doc.org .. [#f5] http://github.com/pybind/python_example + +Calling Python from C++ +======================= + +Pybind11 also allows to call python code from C++. Note that this code assumes, that the intepreter is already initialized. + +.. code-block:: cpp + + // get the main module, so we can access and declare stuff + py::module main_module = py::module::import("__main__"); + + //get the main namespace, so I can declare variables + py::object main_namespace = main_module.attr("__dict__"); + + //now execute code + py::exec( + "print('Hello World1!')\n" + "print('Other Data');", + main_namespace); + + //execute a single statement + py::exec_statement("x=42", main_namespace); + + //ok, now I want to get the result of a statement, we'll use x in this example + py::object res = py::eval("x"); + std:cout << "Yielded: " << res.cast() << std::endl; + + //or we can execute a file within the same content + py::exec_file("my_script.py", main_namespace); + diff --git a/docs/reference.rst b/docs/reference.rst index 4df4344a8..e3fe01849 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -244,3 +244,26 @@ Passing extra arguments to the def function .. function:: name::name(const char *value) Used to specify the function name + +Calling Python from C++ +======================= + +.. function:: eval(str string, object global = object(), object local = object()) + + Evaluate a statement, i.e. one that does not yield None. + The return value the result of the expression. It throws pybind11::error_already_set if the commands are invalid. + +.. function:: exec(str string, object global = object(), object local = object()) + + Execute a set of statements. The return value the result of the code. It throws pybind11::error_already_set if the commands are invalid. + +.. function:: exec_statement(str string, object global = object(), object local = object()) + + Execute a single statement. The return value the result of the code. It throws pybind11::error_already_set if the commands are invalid. + +.. function:: exec_file(str filename, object global = object(), object local = object()) + + Execute a file. The function exec_file will throw std::invalid_argument if the file cannot be opened. + The return value the result of the code. It throws pybind11::error_already_set if the commands are invalid and + std::invalid_argument if the file cannot be opened. + diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 547dad74f..4ac1cb14f 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -24,6 +24,7 @@ set(PYBIND11_EXAMPLES example15.cpp example16.cpp example17.cpp + example18.cpp issues.cpp ) @@ -66,3 +67,4 @@ foreach(VALUE ${PYBIND11_EXAMPLES}) string(REGEX REPLACE "^(.+).cpp$" "\\1" EXAMPLE_NAME "${VALUE}") add_test(NAME ${EXAMPLE_NAME} COMMAND ${RUN_TEST} ${EXAMPLE_NAME}) endforeach() + diff --git a/example/example.cpp b/example/example.cpp index 470684a3b..1f3c18d5e 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -26,6 +26,7 @@ void init_ex14(py::module &); void init_ex15(py::module &); void init_ex16(py::module &); void init_ex17(py::module &); +void init_ex18(py::module &); void init_issues(py::module &); #if defined(PYBIND11_TEST_EIGEN) @@ -52,6 +53,7 @@ PYBIND11_PLUGIN(example) { init_ex15(m); init_ex16(m); init_ex17(m); + init_ex18(m); init_issues(m); #if defined(PYBIND11_TEST_EIGEN) diff --git a/example/example18.cpp b/example/example18.cpp new file mode 100644 index 000000000..f85675d65 --- /dev/null +++ b/example/example18.cpp @@ -0,0 +1,120 @@ +/* + example/example18.cpp -- Usage of exec, eval etc. + + Copyright (c) 2016 Klemens D. Morgenstern + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + + +#include +#include "example.h" + +void example18() { + py::module main_module = py::module::import("__main__"); + py::object main_namespace = main_module.attr("__dict__"); + + bool executed = false; + + main_module.def("call_test", [&]()-> int {executed = true; return 42;}); + + cout << "exec test" << endl; + + py::exec( + "print('Hello World!');\n" + "x = call_test();", + main_namespace); + + if (executed) + cout << "exec passed" << endl; + else { + cout << "exec failed" << endl; + } + + cout << "eval test" << endl; + + py::object val = py::eval("x", main_namespace); + + if (val.cast() == 42) + cout << "eval passed" << endl; + else { + cout << "eval failed" << endl; + } + + + executed = false; + cout << "exec_statement test" << endl; + + py::exec_statement("y = call_test();", main_namespace); + + + if (executed) + cout << "exec_statement passed" << endl; + else { + cout << "exec_statement failed" << endl; + } + + cout << "exec_file test" << endl; + + int val_out; + main_module.def("call_test2", [&](int value) {val_out = value;}); + + + py::exec_file("example18_call.py", main_namespace); + + if (val_out == 42) + cout << "exec_file passed" << endl; + else { + cout << "exec_file failed" << endl; + } + + executed = false; + cout << "exec failure test" << endl; + try { + py::exec("non-sense code ..."); + } + catch (py::error_already_set & err) { + executed = true; + } + if (executed) + cout << "exec failure test passed" << endl; + else { + cout << "exec failure test failed" << endl; + } + + + executed = false; + cout << "exec_file failure test" << endl; + try { + py::exec_file("none-existing file"); + } + catch (std::invalid_argument & err) { + executed = true; + } + if (executed) + cout << "exec_file failure test passed" << endl; + else { + cout << "exec_file failure test failed" << endl; + } + + executed = false; + cout << "eval failure test" << endl; + try { + py::eval("print('dummy')"); + } + catch (py::error_already_set & err) { + executed = true; + } + if (executed) + cout << "eval failure test passed" << endl; + else { + cout << "eval failure test failed" << endl; + } +} + +void init_ex18(py::module & m) { + m.def("example18", &example18); +} + + diff --git a/example/example18.py b/example/example18.py new file mode 100644 index 000000000..314e64de3 --- /dev/null +++ b/example/example18.py @@ -0,0 +1,5 @@ +from example import example18 + +example18() + + diff --git a/example/example18.ref b/example/example18.ref new file mode 100644 index 000000000..81f10491c --- /dev/null +++ b/example/example18.ref @@ -0,0 +1,15 @@ +exec test +Hello World! +exec passed +eval test +eval passed +exec_statement test +exec_statement passed +exec_file test +exec_file passed +exec failure test +exec failure test passed +exec_file failure test +exec_file failure test passed +eval failure test +eval failure test passed diff --git a/example/example18_call.py b/example/example18_call.py new file mode 100644 index 000000000..db96fd3f2 --- /dev/null +++ b/example/example18_call.py @@ -0,0 +1 @@ +call_test2(y) \ No newline at end of file diff --git a/include/pybind11/exec.h b/include/pybind11/exec.h new file mode 100644 index 000000000..3b9ddecdc --- /dev/null +++ b/include/pybind11/exec.h @@ -0,0 +1,156 @@ +/* + pybind11/exec.h: Functions to execute python from C++. Based on code from boost.python. + + Copyright (c) 2005 Stefan Seefeld + + This code is based on the boost.python implementation, so a different license applies to this file. + + Boost Software License - Version 1.0 - August 17th, 2003 + + Permission is hereby granted, free of charge, to any person or organization + obtaining a copy of the software and accompanying documentation covered by + this license (the "Software") to use, reproduce, display, distribute, + execute, and transmit the Software, and to prepare derivative works of the + Software, and to permit third-parties to whom the Software is furnished to + do so, all subject to the following: + + The copyright notices in the Software and this entire statement, including + the above license grant, this restriction and the following disclaimer, + must be included in all copies of the Software, in whole or in part, and + all derivative works of the Software, unless such copies or derivative + works are solely in the form of machine-executable object code generated by + a source language processor. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + +*/ + +#pragma once + +#include "pytypes.h" + +NAMESPACE_BEGIN(pybind11) + + + +inline object eval (const std::string& st, object global = object(), object local = object()) { + if (!global) { + if (PyObject *g = PyEval_GetGlobals()) + global = object(g, true); + else + global = dict(); + } + if (!local) + local = global; + + PyObject *res = PyRun_String(st.c_str() , Py_eval_input, global.ptr(), local.ptr()); + + if (res == nullptr) + throw error_already_set(); + + return {res, false}; +} + +inline object exec (const std::string& st, object global = object(), object local = object()) { + if (!global) { + if (PyObject *g = PyEval_GetGlobals()) + global = object(g, true); + else + global = dict(); + } + if (!local) + local = global; + + PyObject *res = PyRun_String(st.c_str() , Py_file_input, global.ptr(), local.ptr()); + + if (res == nullptr) + throw error_already_set(); + + return {res, false}; +} + +inline object exec_statement (const std::string& st, object global = object(), object local = object()) { + if (!global) { + if (PyObject *g = PyEval_GetGlobals()) + global = object(g, true); + else + global = dict(); + } + if (!local) + local = global; + + PyObject *res = PyRun_String(st.c_str() , Py_single_input, global.ptr(), local.ptr()); + if (res == nullptr) + throw error_already_set(); + + return {res, false}; +} + +inline object exec_file(const std::string& filename, object global = object(), object local = object()) { + // Set suitable default values for global and local dicts. + if (!global) { + if (PyObject *g = PyEval_GetGlobals()) + global = object(g, true); + else + global = dict(); + } + if (!local) local = global; + + std::string f = filename; //need to copy for the signature of PyFile_FromString + + + // Let python open the file to avoid potential binary incompatibilities. +#if PY_VERSION_HEX >= 0x03040000 + const static int close_it = 1; + FILE *fs = _Py_fopen(f.c_str(), "r"); +#elif PY_VERSION_HEX >= 0x03000000 + const static int close_it = 1; + PyObject *fo = Py_BuildValue("s", f.c_str()); + FILE *fs = _Py_fopen(fo, "r"); + Py_DECREF(fo); +#else + const static int close_it = 0; + PyObject *pyfile = PyFile_FromString(&f.front(), const_cast("r")); + if (!pyfile) + throw std::invalid_argument(std::string(f) + " : no such file"); + object file(pyfile, false); + FILE *fs = PyFile_AsFile(file.ptr()); +#endif + if (fs == nullptr) + throw std::invalid_argument(std::string(f) + " : could not be opened"); + + PyObject* res = PyRun_FileEx(fs, + f.c_str(), + Py_file_input, + global.ptr(), local.ptr(), + close_it); + + if (res == nullptr) + throw error_already_set(); + + return {res, false}; + +} + +inline object exec (str string, object global = object(), object local = object()) { + return exec(static_cast(string), global, local); +} + +inline object eval (str string, object global = object(), object local = object()) { + return eval(static_cast(string), global, local); +} + +inline object exec_file(str filename, object global = object(), object local = object()) { + return exec_file(static_cast(filename), global, local); +} +inline object exec_statement (str string, object global = object(), object local = object()) { + return exec_statement(static_cast(string), global, local); +} + +NAMESPACE_END(pybind11)