Skip to content

Commit

Permalink
complete rewrite of eval/exec patch
Browse files Browse the repository at this point in the history
  • Loading branch information
wjakob committed Jul 8, 2016
1 parent c6ad2c4 commit 0d3fc35
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 282 deletions.
52 changes: 29 additions & 23 deletions docs/advanced.rst
Expand Up @@ -282,7 +282,7 @@ helper class that is defined as follows:
The macro :func:`PYBIND11_OVERLOAD_PURE` should be used for pure virtual
functions, and :func:`PYBIND11_OVERLOAD` should be used for functions which have
a default implementation.
a default implementation.

There are also two alternate macros :func:`PYBIND11_OVERLOAD_PURE_NAME` and
:func:`PYBIND11_OVERLOAD_NAME` which take a string-valued name argument
Expand Down Expand Up @@ -1612,32 +1612,38 @@ 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++
=======================
Evaluating Python expressions from strings and files
====================================================

Pybind11 also allows to call python code from C++. Note that this code assumes, that the intepreter is already initialized.
pybind11 provides the :func:`eval` and :func:`eval_file` functions to evaluate
Python expressions and statements. The following example illustrates how they
can be used.

Both functions accept a template parameter that describes how the argument
should be interpreted. Possible choices include ``eval_expr`` (isolated
expression), ``eval_single_statement`` (a single statement, return value is
always ``none``), and ``eval_statements`` (sequence of statements, return value
is always ``none``).

.. 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__");
// At beginning of file
#include <pybind11/eval.h>
...
// Evaluate in scope of main module
py::object scope = py::module::import("__main__").attr("__dict__");
// Evaluate an isolated expression
int result = py::eval("my_variable + 10", scope).cast<int>();
//now execute code
py::exec(
"print('Hello World1!')\n"
"print('Other Data');",
main_namespace);
// Evaluate a sequence of statements
py::eval<py::eval_statements>(
"print('Hello')\n"
"print('world!');",
scope);
//execute a single statement
py::exec_statement("x=42", main_namespace);
// Evaluate the statements in an separate Python file on disk
py::eval_file("script.py", scope);
//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<int>() << std::endl;
//or we can execute a file within the same content
py::exec_file("my_script.py", main_namespace);
22 changes: 0 additions & 22 deletions docs/reference.rst
Expand Up @@ -244,26 +244,4 @@ 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.

122 changes: 52 additions & 70 deletions example/example18.cpp
@@ -1,5 +1,5 @@
/*
example/example18.cpp -- Usage of exec, eval etc.
example/example18.cpp -- Usage of eval() and eval_file()
Copyright (c) 2016 Klemens D. Morgenstern
Expand All @@ -8,113 +8,95 @@
*/


#include <pybind11/exec.h>
#include <pybind11/eval.h>
#include "example.h"

void example18() {
py::module main_module = py::module::import("__main__");
py::object main_namespace = main_module.attr("__dict__");

bool executed = false;
bool ok = false;

main_module.def("call_test", [&]()-> int {executed = true; return 42;});
main_module.def("call_test", [&]() -> int {
ok = true;
return 42;
});

cout << "exec test" << endl;
cout << "eval_statements test" << endl;

py::exec(
auto result = py::eval<py::eval_statements>(
"print('Hello World!');\n"
"x = call_test();",
main_namespace);
"x = call_test();", main_namespace);

if (executed)
cout << "exec passed" << endl;
else {
cout << "exec failed" << endl;
}
if (ok && result == py::none())
cout << "eval_statements passed" << endl;
else
cout << "eval_statements failed" << endl;

cout << "eval test" << endl;

py::object val = py::eval("x", main_namespace);

if (val.cast<int>() == 42)
cout << "eval passed" << endl;
else {
else
cout << "eval failed" << endl;
}


executed = false;
cout << "exec_statement test" << endl;
ok = false;
cout << "eval_single_statement test" << endl;

py::exec_statement("y = call_test();", main_namespace);
py::eval<py::eval_single_statement>(
"y = call_test();", main_namespace);

if (ok)
cout << "eval_single_statement passed" << endl;
else
cout << "eval_single_statement failed" << endl;

if (executed)
cout << "exec_statement passed" << endl;
else {
cout << "exec_statement failed" << endl;
}

cout << "exec_file test" << endl;
cout << "eval_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;
result = py::eval_file("example18_call.py", main_namespace);
} catch (...) {
result = py::eval_file("example/example18_call.py", main_namespace);
}

if (val_out == 42 && result == py::none())
cout << "eval_file passed" << endl;
else
cout << "eval_file 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;
ok = false;
cout << "eval failure test" << endl;
try {
py::eval("print('dummy')");
}
catch (py::error_already_set & err) {
executed = true;
py::eval("nonsense code ...");
} catch (py::error_already_set &) {
PyErr_Clear();
ok = true;
}
if (executed)

if (ok)
cout << "eval failure test passed" << endl;
else {
else
cout << "eval failure test failed" << endl;

ok = false;
cout << "eval_file failure test" << endl;
try {
py::eval_file("nonexisting file");
} catch (std::exception &) {
ok = true;
}

if (ok)
cout << "eval_file failure test passed" << endl;
else
cout << "eval_file failure test failed" << endl;
}

void init_ex18(py::module & m) {
m.def("example18", &example18);
m.def("example18", &example18);
}


20 changes: 9 additions & 11 deletions example/example18.ref
@@ -1,15 +1,13 @@
exec test
Hello World!
exec passed
eval_statements test
eval_statements 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_single_statement test
eval_single_statement passed
eval_file test
eval_file passed
eval failure test
eval failure test passed
eval_file failure test
eval_file failure test passed
Hello World!
88 changes: 88 additions & 0 deletions include/pybind11/eval.h
@@ -0,0 +1,88 @@
/*
pybind11/exec.h: Support for evaluating Python expressions and statements
from strings and files
Copyright (c) 2016 Klemens Morgenstern <klemens.morgenstern@ed-chemnitz.de> and
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

#pragma once

#include "pytypes.h"

NAMESPACE_BEGIN(pybind11)

enum eval_mode {
/// Evaluate a string containing an isolated expression
eval_expr,

/// Evaluate a string containing a single statement. Returns \c none
eval_single_statement,

/// Evaluate a string containing a sequence of statement. Returns \c none
eval_statements
};

template <eval_mode mode = eval_expr>
object eval(const std::string& str, object global = object(), object local = object()) {
if (!global) {
global = object(PyEval_GetGlobals(), true);
if (!global)
global = dict();
}
if (!local)
local = global;

int start;
switch (mode) {
case eval_expr: start = Py_eval_input; break;
case eval_single_statement: start = Py_single_input; break;
case eval_statements: start = Py_file_input; break;
default: pybind11_fail("invalid evaluation mode");
}

object result(PyRun_String(str.c_str(), start, global.ptr(), local.ptr()), false);

if (!result)
throw error_already_set();
return result;
}

template <eval_mode mode = eval_statements>
object eval_file(const std::string& fname, object global = object(), object local = object()) {
if (!global) {
global = object(PyEval_GetGlobals(), true);
if (!global)
global = dict();
}
if (!local)
local = global;

int start;
switch (mode) {
case eval_expr: start = Py_eval_input; break;
case eval_single_statement: start = Py_single_input; break;
case eval_statements: start = Py_file_input; break;
default: pybind11_fail("invalid evaluation mode");
}

FILE *f = fopen(fname.c_str(), "r");
if (!f)
pybind11_fail("File \"" + fname + "\" could not be opened!");

object result(PyRun_FileEx(f, fname.c_str(), Py_file_input, global.ptr(),
local.ptr(), 1),
false);

if (!result)
throw error_already_set();

return result;
}

NAMESPACE_END(pybind11)

0 comments on commit 0d3fc35

Please sign in to comment.