-
Book Overview & Buying
-
Table Of Contents
IPython Interactive Computing and Visualization Cookbook
By :
The architecture that has been developed for IPython and that will be the core of Project Jupyter is becoming increasingly language independent. The decoupling between the client and kernel makes it possible to write kernels in any language. The client communicates with the kernel via socket-based messaging protocols. Thus, a kernel can be written in any language that supports sockets.
However, the messaging protocols are complex. Writing a new kernel from scratch is not straightforward. Fortunately, IPython 3.0 brings a lightweight interface for kernel languages that can be wrapped in Python.
This interface can also be used to create an entirely customized experience in the IPython notebook (or another client application such as the console). Normally, Python code has to be written in every code cell; however, we can write a kernel for any domain-specific language. We just have to write a Python function that accepts a code string as input (the contents of the code cell), and sends text or rich data as output. We can also easily implement code completion and code inspection.
We can imagine many interesting interactive applications that go far beyond the original use cases of IPython. These applications might be particularly useful for nonprogrammer end users such as high school students.
In this recipe, we will create a simple graphing calculator. The calculator is transparently backed by NumPy and matplotlib. We just have to write functions as y = f(x) in a code cell to get a graph of these functions.
This recipe has been tested on the development version of IPython 3.0. It should work on the final version of IPython 3.0 with no or minimal changes. We give all references about wrapper kernels and messaging protocols at the end of this recipe.
Warning: This recipe works only on IPython >= 3.0!
plotkernel.py file. This file will contain the implementation of our custom kernel. Let's import a few modules:Be sure to put the code in steps 1-6 in an external text file named plotkernel.py, rather than in the notebook's input!
from IPython.kernel.zmq.kernelbase import Kernel import numpy as np import matplotlib.pyplot as plt from io import BytesIO import urllib, base64
def _to_png(fig):
"""Return a base64-encoded PNG from a
matplotlib figure."""
imgdata = BytesIO()
fig.savefig(imgdata, format='png')
imgdata.seek(0)
return urllib.parse.quote(
base64.b64encode(imgdata.getvalue()))y = f(x), and returns a NumPy function. Here, f is an arbitrary Python expression that can use NumPy functions:_numpy_namespace = {n: getattr(np, n)
for n in dir(np)}
def _parse_function(code):
"""Return a NumPy function from a string 'y=f(x)'."""
return lambda x: eval(code.split('=')[1].strip(),
_numpy_namespace, {'x': x})Kernel. There are a few metadata fields we need to provide:class PlotKernel(Kernel):
implementation = 'Plot'
implementation_version = '1.0'
language = 'python' # will be used for
# syntax highlighting
language_version = ''
banner = "Simple plotting"do_execute() method that takes code as input and sends responses to the client:def do_execute(self, code, silent,
store_history=True,
user_expressions=None,
allow_stdin=False):
# We create the plot with matplotlib.
fig = plt.figure(figsize=(6,4), dpi=100)
x = np.linspace(-5., 5., 200)
functions = code.split('\n')
for fun in functions:
f = _parse_function(fun)
y = f(x)
plt.plot(x, y)
plt.xlim(-5, 5)
# We create a PNG out of this plot.
png = _to_png(fig)
if not silent:
# We send the standard output to the client.
self.send_response(self.iopub_socket,
'stream', {
'name': 'stdout',
'data': 'Plotting {n} function(s)'. \
format(n=len(functions))})
# We prepare the response with our rich data
# (the plot).
content = {
'source': 'kernel',
# This dictionary may contain different
# MIME representations of the output.
'data': {
'image/png': png
},
# We can specify the image size
# in the metadata field.
'metadata' : {
'image/png' : {
'width': 600,
'height': 400
}
}
}
# We send the display_data message with the
# contents.
self.send_response(self.iopub_socket,
'display_data', content)
# We return the execution results.
return {'status': 'ok',
'execution_count': self.execution_count,
'payload': [],
'user_expressions': {},
}if __name__ == '__main__':
from IPython.kernel.zmq.kernelapp import IPKernelApp
IPKernelApp.launch_instance(kernel_class=PlotKernel)kernel.json file and put it in ~/.ipython/kernels/plot/. This file contains the following lines:{
"argv": ["python", "-m",
"plotkernel", "-f",
"{connection_file}"],
"display_name": "Plot",
"language": "python"
}The plotkernel.py file needs to be importable by Python. For example, we could simply put it in the current directory.
y = f(x). The corresponding graph appears in the output area. Here is an example:
Example of our custom plot wrapper kernel
We will give more details about the architecture of IPython and the notebook in Chapter 3, Mastering the Notebook. We will just give a summary here. Note that these details might change in future versions of IPython.
The kernel and client live in different processes. They communicate via messaging protocols implemented on top of network sockets. Currently, these messages are encoded in JSON, a structured, text-based document format.
Our kernel receives code from the client (the notebook, for example). The do_execute()function is called whenever the user sends a cell's code.
The kernel can send messages back to the client with the self.send_response() method:
stream, to send back standard output or a standard error, or display_data to send back rich dataThe data can contain multiple MIME representations: text, HTML, SVG, images, and others. It is up to the client to handle these data types. In particular, the HTML notebook client knows how to represent all these types in the browser.
The function returns execution results in a dictionary.
In this toy example, we always return an ok status. In production code, it would be a good idea to detect errors (syntax errors in the function definitions, for example) and return an error status instead.
All messaging protocol details can be found at the links given at the end of this recipe.
Wrapper kernels can implement optional methods, notably for code completion and code inspection. For example, to implement code completion, we need to write the following method:
def do_complete(self, code, cursor_pos):
return {'status': 'ok',
'cursor_start': ...,
'cursor_end': ...,
'matches': [...]}This method is called whenever the user requests code completion when the cursor is at a given cursor_pos location in the code cell. In the method's response, the cursor_start and cursor_end fields represent the interval that code completion should overwrite in the output. The matches field contains the list of suggestions.
These details might have changed by the time IPython 3.0 is released. You will find all up-to-date information in the following references:
Change the font size
Change margin width
Change background colour