{ "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 IPython 4. It should work on IPython 3 with 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", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%%writefile plotkernel.py\n", "# NOTE: We create the `plotkernel.py` file here so that \n", "# you don't have to do it...\n", "from ipykernel.kernelbase import Kernel\n", "#from IPython.kernel.zmq.kernelbase import Kernel # IPython < 4.x\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", " banner = \"Simple plotting\"\n", " language_info = {\n", " 'name': 'python', # will be used for syntax highlighting\n", " 'version': '',\n", " 'file_extension': '.plot',\n", " 'mimetype': 'text/x-python'\n", " }\n", " # language and language_version needed only for protocol version < 5.0\n", " language = language_info['name']\n", " language_version = language_info['version']\n", "\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', # IPython < 4.x\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 ipykernel.kernelapp import IPKernelApp\n", " #from IPython.kernel.zmq.kernelapp import IPKernelApp # IPython < 4.x\n", " IPKernelApp.launch_instance(kernel_class=PlotKernel)" ] }, { "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": [ "```python\n", "from ipykernel.kernelbase import Kernel\n", "#from IPython.kernel.zmq.kernelbase import Kernel # IPython < 4.x\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": [ "```python\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": [ "```python\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": [ "```python\n", "class PlotKernel(Kernel):\n", " implementation = 'Plot'\n", " implementation_version = '1.0'\n", " banner = \"Simple plotting\"\n", " language_info = {\n", " 'name': 'python', # will be used for syntax highlighting\n", " 'version': '',\n", " 'file_extension': '.plot',\n", " 'mimetype': 'text/x-python'\n", " }\n", " # language and language_version needed only for protocol version < 5.0\n", " language = language_info['name']\n", " language_version = language_info['version']\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": [ "```python\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": [ "```python\n", "if __name__ == '__main__':\n", " from ipykernel.kernelapp import IPKernelApp\n", " #from IPython.kernel.zmq.kernelapp import IPKernelApp # IPython < 4.x\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 in a directory named after our kernel." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "!mkdir plot" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `kernel.json` file contains the following lines:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%%writefile plot/kernel.json\n", "{\n", " \"argv\": [\"python\", \"-m\",\n", " \"plotkernel\", \"-f\",\n", " \"{connection_file}\"],\n", " \"display_name\": \"Plot\",\n", " \"language\": \"python\"\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you are using IPython 3, simply move the directory `plot` (with `kernel.json` in it) to `~/.ipython/kernels/`. This may still work (deprecated) for IPython 4, but since IPython 4 is Jupyter based, the proper way is to copy the whole directory to the appropriate location using the `jupyter` command:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "!jupyter kernelspec install plot --user --replace" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The main argument to this command is the path to the directory containing the kernel spec. The option `--user` instructs Jupyter to install the kernel for the current user only. The option `--replace` overwrites any already installed kernel with the same name (if exists).\n", "\n", "The installation can be verified by listing all kernels known to Jupyter and their locations:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "!jupyter kernelspec list" ] }, { "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 4, you can launch a notebook with this kernel from the IPython notebook dashboard, in a dropdown menu from the ‘New’ button.\n", "\n", "In IPython 3, this feature may not be available in all versions. An alternative (deprecated) 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": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.0" } }, "nbformat": 4, "nbformat_minor": 0 }