Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ set(XEUSCLING_SRC
src/main.cpp
src/xpython_interpreter.cpp
src/xpython_logger.cpp
src/xpython_display.cpp
src/xpyt_config.hpp
)

Expand Down
62 changes: 61 additions & 1 deletion notebooks/xeus-python.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"metadata": {},
"outputs": [],
"source": [
"a = 3"
"a = 3\n",
"a"
]
},
{
Expand All @@ -18,6 +19,56 @@
"a"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"a += 6"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(a)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"a"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"b = 89\n",
"\n",
"def test(x):\n",
" return x * 2\n",
"\n",
"test(b)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"test(4)"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -45,6 +96,15 @@
"b = {\"a\": 2, \"b\": 3}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"b"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down
52 changes: 52 additions & 0 deletions src/xpython_display.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/***************************************************************************
* Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay and *
* Wolf Vollprecht *
* *
* Distributed under the terms of the BSD 3-Clause License. *
* *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "pybind11/pybind11.h"
#include "pybind11/embed.h"

#include "xpython_display.hpp"

namespace py = pybind11;

namespace xpyt
{
xdisplayhook::xdisplayhook() : m_execution_count(0)
{
}

xdisplayhook::~xdisplayhook()
{
}

void xdisplayhook::set_execution_count(int execution_count)
{
m_execution_count = execution_count;
}

void xdisplayhook::add_hook(hook_function_type hook)
{
m_hooks.push_back(hook);
}

void xdisplayhook::operator()(py::object obj)
{
for (auto it = m_hooks.begin(); it != m_hooks.end(); ++it)
{
it->operator()(m_execution_count, obj);
}
}

PYBIND11_EMBEDDED_MODULE(xeus_python_display, m) {
py::class_<xdisplayhook>(m, "XPythonDisplay")
.def(py::init<>())
.def("set_execution_count", &xdisplayhook::set_execution_count)
.def("add_hook", &xdisplayhook::add_hook)
.def("__call__", &xdisplayhook::operator());
}
}
44 changes: 44 additions & 0 deletions src/xpython_display.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/***************************************************************************
* Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay and *
* Wolf Vollprecht *
* *
* Distributed under the terms of the BSD 3-Clause License. *
* *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#ifndef XPYT_PYTHON_DISPLAY_HPP
#define XPYT_PYTHON_DISPLAY_HPP

#include <functional>

#include "pybind11/pybind11.h"
#include "pybind11/functional.h"

namespace py = pybind11;

namespace xpyt
{
class xdisplayhook
{

public:

using hook_function_type = std::function<void(int, py::object)>;
using hooks_type = std::vector<hook_function_type>;

xdisplayhook();
virtual ~xdisplayhook();

void set_execution_count(int execution_count);
void add_hook(hook_function_type hook);
void operator()(py::object obj);

private:

int m_execution_count;
hooks_type m_hooks;
};
}

#endif
57 changes: 56 additions & 1 deletion src/xpython_interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace xpyt
py::initialize_interpreter();

redirect_output();
redirect_display();
}

xpython_interpreter::~xpython_interpreter() {}
Expand All @@ -49,11 +50,44 @@ namespace xpyt

try
{
py::exec(code);
// Import AST ans builtins modules
py::module ast = py::module::import("ast");
py::module builtins = py::module::import("builtins");

// Parse code to AST
py::object code_ast = ast.attr("parse")(code, "<string>", "exec");
py::list expressions = code_ast.attr("body");

// If the last statement is an expression, we compile it seperately
// in an interactive mode (This will trigger the display hook)
py::object last_stmt = expressions[py::len(expressions) - 1];
if (py::isinstance(last_stmt, ast.attr("Expr")))
{
code_ast.attr("body").attr("pop")();

py::list interactive_nodes;
interactive_nodes.append(last_stmt);

py::object interactive_ast = ast.attr("Interactive")(interactive_nodes);

py::object compiled_code = builtins.attr("compile")(code_ast, "<ast>", "exec");
py::object compiled_interactive_code = builtins.attr("compile")(interactive_ast, "<ast>", "single");

m_displayhook.attr("set_execution_count")(execution_counter);

builtins.attr("exec")(compiled_code, py::globals());
builtins.attr("exec")(compiled_interactive_code, py::globals());
}
else
{
py::object compiled_code = builtins.attr("compile")(code_ast, "<ast>", "exec");
builtins.attr("exec")(compiled_code, py::globals());
}

Copy link
Member Author

@martinRenou martinRenou Nov 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Carreau would you mind telling me what you think about that code? What I want to do is display the last expression of the cell (e.g. "Out [3]: ...")

What I do is:

  • I use the AST to extract the last expression of the cell
  • I execute the remaining expressions normally
  • I execute the last expression in an Interactive way

Do you think it's how it should be done?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is the same as what we do in IPython. The smaller difference is that we loop of the n-1 non-final statement manually. Not sure it makes a difference.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note we also have other mode where

  • all the top-level expressions trigger display
  • the last assigment also trigger display
  • Async-mode

But those can be done later or not at all I guess.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your reply! When are those modes used? Is it a command line option? (I can't find it)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are both CLI and dynamic options. Rarely used. So I wouldn't worry about theses yet.

kernel_res["status"] = "ok";
kernel_res["payload"] = xeus::xjson::array();
kernel_res["user_expressions"] = xeus::xjson::object();

} catch(const std::exception& e) {

std::string ename = "Execution error";
Expand Down Expand Up @@ -167,4 +201,25 @@ namespace xpyt
sys.attr("stdout") = out_logger;
sys.attr("stderr") = err_logger;
}

void xpython_interpreter::redirect_display()
{
py::module sys = py::module::import("sys");

py::module xeus_python_display = py::module::import("xeus_python_display");
m_displayhook = xeus_python_display.attr("XPythonDisplay")();

py::cpp_function publish_display = [this](int execution_counter, py::object obj){
if (!obj.is_none())
{
xeus::xjson pub_data;
pub_data["text/plain"] = py::str(obj);
publish_execution_result(execution_counter, std::move(pub_data), xeus::xjson::object());
}
};

m_displayhook.attr("add_hook")(publish_display);

sys.attr("displayhook") = m_displayhook;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Carreau And here I overwrite sys.displayhook which is called when the interactive code is executed, my displayhook publish the execution result to clients.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

}
7 changes: 7 additions & 0 deletions src/xpython_interpreter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@

#include <string>

#include "pybind11/pybind11.h"

#include "xeus/xjson.hpp"
#include "xeus/xinterpreter.hpp"

namespace py = pybind11;

namespace xpyt
{
class xpython_interpreter : public xeus::xinterpreter
Expand Down Expand Up @@ -57,6 +61,9 @@ namespace xpyt
void input_reply_impl(const std::string& value) override;

void redirect_output();
void redirect_display();

py::object m_displayhook;
};
}

Expand Down
1 change: 1 addition & 0 deletions src/xpython_logger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <string>
#include <vector>
#include <functional>

namespace xpyt
{
Expand Down