{ "metadata": { "name": "", "signature": "sha256:c04df6ef396ba1288bc94e1bb1bef43c72dc8af6a22dc50e44ddee5714efe5be" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "> This is one of the 100 recipes of the [IPython Cookbook](http://ipython-books.github.io/), the definitive guide to high-performance scientific computing and data science in Python." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 1.6. Creating a simple kernel for IPython" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This recipe has been tested on the development version of IPython 3. It should work on the final version of IPython 3 with no or minimal changes. We give all references about wrapper kernels and messaging protocols at the end of this recipe." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Besides, the code given here works with Python 3. It can be ported to Python 2 with minimal efforts." ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%writefile plotkernel.py\n", "# NOTE: We create the `plotkernel.py` file here so that \n", "# you don't have to do it...\n", "from IPython.kernel.zmq.kernelbase import Kernel\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from io import BytesIO\n", "import urllib, base64\n", "\n", "def _to_png(fig):\n", " \"\"\"Return a base64-encoded PNG from a \n", " matplotlib figure.\"\"\"\n", " imgdata = BytesIO()\n", " fig.savefig(imgdata, format='png')\n", " imgdata.seek(0)\n", " return urllib.parse.quote(\n", " base64.b64encode(imgdata.getvalue()))\n", "\n", "_numpy_namespace = {n: getattr(np, n) \n", " for n in dir(np)}\n", "def _parse_function(code):\n", " \"\"\"Return a NumPy function from a string 'y=f(x)'.\"\"\"\n", " return lambda x: eval(code.split('=')[1].strip(),\n", " _numpy_namespace, {'x': x})\n", "\n", "class PlotKernel(Kernel):\n", " implementation = 'Plot'\n", " implementation_version = '1.0'\n", " language = 'python' # will be used for\n", " # syntax highlighting\n", " language_version = ''\n", " banner = \"Simple plotting\"\n", " \n", " def do_execute(self, code, silent,\n", " store_history=True,\n", " user_expressions=None,\n", " allow_stdin=False):\n", "\n", " # We create the plot with matplotlib.\n", " fig = plt.figure(figsize=(6,4), dpi=100)\n", " x = np.linspace(-5., 5., 200)\n", " functions = code.split('\\n')\n", " for fun in functions:\n", " f = _parse_function(fun)\n", " y = f(x)\n", " plt.plot(x, y)\n", " plt.xlim(-5, 5)\n", "\n", " # We create a PNG out of this plot.\n", " png = _to_png(fig)\n", "\n", " if not silent:\n", " # We send the standard output to the client.\n", " self.send_response(self.iopub_socket,\n", " 'stream', {\n", " 'name': 'stdout', \n", " 'data': 'Plotting {n} function(s)'. \\\n", " format(n=len(functions))})\n", "\n", " # We prepare the response with our rich data\n", " # (the plot).\n", " content = {\n", " 'source': 'kernel',\n", "\n", " # This dictionary may contain different\n", " # MIME representations of the output.\n", " 'data': {\n", " 'image/png': png\n", " },\n", "\n", " # We can specify the image size\n", " # in the metadata field.\n", " 'metadata' : {\n", " 'image/png' : {\n", " 'width': 600,\n", " 'height': 400\n", " }\n", " }\n", " } \n", "\n", " # We send the display_data message with the\n", " # contents.\n", " self.send_response(self.iopub_socket,\n", " 'display_data', content)\n", "\n", " # We return the exection results.\n", " return {'status': 'ok',\n", " 'execution_count': self.execution_count,\n", " 'payload': [],\n", " 'user_expressions': {},\n", " }\n", "\n", "if __name__ == '__main__':\n", " from IPython.kernel.zmq.kernelapp import IPKernelApp\n", " IPKernelApp.launch_instance(kernel_class=PlotKernel)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. First, we create a file `plotkernel.py`. This file will contain the implementation of our custom kernel. Let's import a few modules." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "from IPython.kernel.zmq.kernelbase import Kernel\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from io import BytesIO\n", "import urllib, base64```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2. We write a function that returns a PNG base64-encoded representation of a matplotlib figure." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "def _to_png(fig):\n", " \"\"\"Return a base64-encoded PNG from a \n", " matplotlib figure.\"\"\"\n", " imgdata = BytesIO()\n", " fig.savefig(imgdata, format='png')\n", " imgdata.seek(0)\n", " return urllib.parse.quote(\n", " base64.b64encode(imgdata.getvalue()))```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "3. Now, we write a function that parses a code string which has the form `y = f(x)`, and returns a NumPy function. Here, `f` is an arbitrary Python expression that can use NumPy functions." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "_numpy_namespace = {n: getattr(np, n) \n", " for n in dir(np)}\n", "def _parse_function(code):\n", " \"\"\"Return a NumPy function from a string 'y=f(x)'.\"\"\"\n", " return lambda x: eval(code.split('=')[1].strip(),\n", " _numpy_namespace, {'x': x})```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "4. For our new wrapper kernel, we create a class deriving from `Kernel`. There are a few metadata fields we need to provide." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "class PlotKernel(Kernel):\n", " implementation = 'Plot'\n", " implementation_version = '1.0'\n", " language = 'python' # will be used for\n", " # syntax highlighting\n", " language_version = ''\n", " banner = \"Simple plotting\"\n", " ```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "5. In this class, we implement a `do_execute()` method that takes code as input, and sends responses to the client." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "def do_execute(self, code, silent,\n", " store_history=True,\n", " user_expressions=None,\n", " allow_stdin=False):\n", "\n", " # We create the plot with matplotlib.\n", " fig = plt.figure(figsize=(6,4), dpi=100)\n", " x = np.linspace(-5., 5., 200)\n", " functions = code.split('\\n')\n", " for fun in functions:\n", " f = _parse_function(fun)\n", " y = f(x)\n", " plt.plot(x, y)\n", " plt.xlim(-5, 5)\n", "\n", " # We create a PNG out of this plot.\n", " png = _to_png(fig)\n", "\n", " if not silent:\n", " # We send the standard output to the client.\n", " self.send_response(self.iopub_socket,\n", " 'stream', {\n", " 'name': 'stdout', \n", " 'data': 'Plotting {n} function(s)'. \\\n", " format(n=len(functions))})\n", "\n", " # We prepare the response with our rich data\n", " # (the plot).\n", " content = {\n", " 'source': 'kernel',\n", "\n", " # This dictionary may contain different\n", " # MIME representations of the output.\n", " 'data': {\n", " 'image/png': png\n", " },\n", "\n", " # We can specify the image size\n", " # in the metadata field.\n", " 'metadata' : {\n", " 'image/png' : {\n", " 'width': 600,\n", " 'height': 400\n", " }\n", " }\n", " } \n", "\n", " # We send the display_data message with the\n", " # contents.\n", " self.send_response(self.iopub_socket,\n", " 'display_data', content)\n", "\n", " # We return the exection results.\n", " return {'status': 'ok',\n", " 'execution_count': self.execution_count,\n", " 'payload': [],\n", " 'user_expressions': {},\n", " }```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "6. Finally, we add the following lines at the end of the file." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "if __name__ == '__main__':\n", " from IPython.kernel.zmq.kernelapp import IPKernelApp\n", " IPKernelApp.launch_instance(kernel_class=PlotKernel)```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "7. Our kernel is ready! The next step is to indicate to IPython that this new kernel is available. To do this, we need to create a **kernel spec** `kernel.json` file and put it in `~/.ipython/kernels/plot/`. This file contains the following lines:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "{\n", " \"argv\": [\"python\", \"-m\",\n", " \"plotkernel\", \"-f\",\n", " \"{connection_file}\"],\n", " \"display_name\": \"Plot\",\n", " \"language\": \"python\"\n", "}```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `plotkernel.py` file needs to be importable by Python. For example, you could simply put it in the current directory." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "8. In IPython 3, you can launch a notebook with this kernel from the IPython notebook dashboard. However, this feature is not available at the time of writing. An alternative (that is probably going to be deprecated by the time IPython 3 is released) is to run the following command in a terminal:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "ipython notebook --KernelManager.kernel_cmd=\"['python', '-m', 'plotkernel', '-f', '{connection_file}']\"\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "9. Finally, in a new notebook backed by our custom plot kernel, we can simply write mathematical equations `y=f(x)`. The corresponding graph appears in the output area." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> You'll find all the explanations, figures, references, and much more in the book (to be released later this summer).\n", "\n", "> [IPython Cookbook](http://ipython-books.github.io/), by [Cyrille Rossant](http://cyrille.rossant.net), Packt Publishing, 2014 (500 pages)." ] } ], "metadata": {} } ] }