display_protocol.ipynb
403 lines
| 12.5 KiB
| text/plain
|
TextLexer
Fernando Perez
|
r4776 | { | |
Fernando Perez
|
r5785 | "metadata": { | |
"name": "display_protocol" | |||
Brian Granger
|
r6035 | }, | |
"nbformat": 3, | |||
MinRK
|
r7739 | "nbformat_minor": 0, | |
Fernando Perez
|
r5785 | "worksheets": [ | |
{ | |||
"cells": [ | |||
{ | |||
MinRK
|
r7739 | "cell_type": "heading", | |
"level": 1, | |||
"metadata": {}, | |||
"source": [ | |||
"Using the IPython display protocol for your own objects" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"IPython extends the idea of the ``__repr__`` method in Python to support multiple representations for a given\n", | |||
"object, which clients can use to display the object according to their capabilities. An object can return multiple\n", | |||
"representations of itself by implementing special methods, and you can also define at runtime custom display \n", | |||
"functions for existing objects whose methods you can't or won't modify. In this notebook, we show how both approaches work.\n", | |||
"\n", | |||
"<br/>\n", | |||
"**Note:** this notebook has had all output cells stripped out so we can include it in the IPython documentation with \n", | |||
"a minimal file size. You'll need to manually execute the cells to see the output (you can run all of them with the \n", | |||
"\"Run All\" button, or execute each individually)." | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Parts of this notebook need the inline matplotlib backend:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"%pylab inline" | |||
], | |||
"language": "python", | |||
"metadata": {}, | |||
"outputs": [] | |||
}, | |||
{ | |||
"cell_type": "heading", | |||
"level": 2, | |||
"metadata": {}, | |||
"source": [ | |||
"Custom-built classes with dedicated ``_repr_*_`` methods" | |||
] | |||
}, | |||
{ | |||
Brian Granger
|
r6035 | "cell_type": "markdown", | |
MinRK
|
r7739 | "metadata": {}, | |
Fernando Perez
|
r5785 | "source": [ | |
MinRK
|
r7739 | "In our first example, we illustrate how objects can expose directly to IPython special representations of\n", | |
"themselves, by providing methods such as ``_repr_svg_``, ``_repr_png_``, ``_repr_latex_``, etc. For a full\n", | |||
"list of the special ``_repr_*_`` methods supported, see the code in ``IPython.core.displaypub``.\n", | |||
"\n", | |||
"As an illustration, we build a class that holds data generated by sampling a Gaussian distribution with given mean \n", | |||
"and variance. The class can display itself in a variety of ways: as a LaTeX expression or as an image in PNG or SVG \n", | |||
"format. Each frontend can then decide which representation it can handle.\n", | |||
"Further, we illustrate how to expose directly to the user the ability to directly access the various alternate \n", | |||
"representations (since by default displaying the object itself will only show one, and which is shown will depend on the \n", | |||
"required representations that even cache necessary data in cases where it may be expensive to compute.\n", | |||
"\n", | |||
Fernando Perez
|
r5785 | "The next cell defines the Gaussian class:" | |
] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
MinRK
|
r7739 | "from IPython.core.pylabtools import print_figure\n", | |
"from IPython.display import Image, SVG, Math\n", | |||
"\n", | |||
"class Gaussian(object):\n", | |||
" \"\"\"A simple object holding data sampled from a Gaussian distribution.\n", | |||
" \"\"\"\n", | |||
" def __init__(self, mean=0, std=1, size=1000):\n", | |||
" self.data = np.random.normal(mean, std, size)\n", | |||
" self.mean = mean\n", | |||
" self.std = std\n", | |||
" self.size = size\n", | |||
" # For caching plots that may be expensive to compute\n", | |||
" self._png_data = None\n", | |||
" self._svg_data = None\n", | |||
" \n", | |||
" def _figure_data(self, format):\n", | |||
" fig, ax = plt.subplots()\n", | |||
" ax.plot(self.data, 'o')\n", | |||
" ax.set_title(self._repr_latex_())\n", | |||
" data = print_figure(fig, format)\n", | |||
" # We MUST close the figure, otherwise IPython's display machinery\n", | |||
" # will pick it up and send it as output, resulting in a double display\n", | |||
" plt.close(fig)\n", | |||
" return data\n", | |||
" \n", | |||
" # Here we define the special repr methods that provide the IPython display protocol\n", | |||
" # Note that for the two figures, we cache the figure data once computed.\n", | |||
" \n", | |||
" def _repr_png_(self):\n", | |||
" if self._png_data is None:\n", | |||
" self._png_data = self._figure_data('png')\n", | |||
" return self._png_data\n", | |||
"\n", | |||
"\n", | |||
" def _repr_svg_(self):\n", | |||
" if self._svg_data is None:\n", | |||
" self._svg_data = self._figure_data('svg')\n", | |||
" return self._svg_data\n", | |||
" \n", | |||
" def _repr_latex_(self):\n", | |||
" return r'$\\mathcal{N}(\\mu=%.2g, \\sigma=%.2g),\\ N=%d$' % (self.mean,\n", | |||
" self.std, self.size)\n", | |||
" \n", | |||
" # We expose as properties some of the above reprs, so that the user can see them\n", | |||
" # directly (since otherwise the client dictates which one it shows by default)\n", | |||
" @property\n", | |||
" def png(self):\n", | |||
" return Image(self._repr_png_(), embed=True)\n", | |||
" \n", | |||
" @property\n", | |||
" def svg(self):\n", | |||
" return SVG(self._repr_svg_())\n", | |||
" \n", | |||
" @property\n", | |||
" def latex(self):\n", | |||
" return Math(self._repr_svg_())\n", | |||
" \n", | |||
" # An example of using a property to display rich information, in this case\n", | |||
" # the histogram of the distribution. We've hardcoded the format to be png\n", | |||
" # in this case, but in production code it would be trivial to make it an option\n", | |||
" @property\n", | |||
" def hist(self):\n", | |||
" fig, ax = plt.subplots()\n", | |||
" ax.hist(self.data, bins=100)\n", | |||
" ax.set_title(self._repr_latex_())\n", | |||
" data = print_figure(fig, 'png')\n", | |||
" plt.close(fig)\n", | |||
Fernando Perez
|
r5785 | " return Image(data, embed=True)" | |
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "markdown", | |
MinRK
|
r7739 | "metadata": {}, | |
Fernando Perez
|
r5785 | "source": [ | |
"Now, we create an instance of the Gaussian distribution, whose default representation will be its LaTeX form:" | |||
] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
MinRK
|
r7739 | "x = Gaussian()\n", | |
Fernando Perez
|
r5785 | "x" | |
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "markdown", | |
MinRK
|
r7739 | "metadata": {}, | |
Fernando Perez
|
r5785 | "source": [ | |
"We can view the data in png or svg formats:" | |||
] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
"x.png" | |||
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
"x.svg" | |||
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "markdown", | |
MinRK
|
r7739 | "metadata": {}, | |
Fernando Perez
|
r5785 | "source": [ | |
MinRK
|
r7739 | "Since IPython only displays by default as an ``Out[]`` cell the result of the last computation, we can use the\n", | |
Fernando Perez
|
r5785 | "``display()`` function to show more than one representation in a single cell:" | |
] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
MinRK
|
r7739 | "display(x.png)\n", | |
Fernando Perez
|
r5785 | "display(x.svg)" | |
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "markdown", | |
MinRK
|
r7739 | "metadata": {}, | |
Fernando Perez
|
r5785 | "source": [ | |
"Now let's create a new Gaussian with different parameters" | |||
] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
MinRK
|
r7739 | "x2 = Gaussian(0.5, 0.2, 2000)\n", | |
Fernando Perez
|
r5785 | "x2" | |
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "markdown", | |
MinRK
|
r7739 | "metadata": {}, | |
Fernando Perez
|
r5785 | "source": [ | |
"We can easily compare them by displaying their histograms" | |||
] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
MinRK
|
r7739 | "display(x.hist)\n", | |
Fernando Perez
|
r5785 | "display(x2.hist)" | |
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "markdown", | |
MinRK
|
r7739 | "metadata": {}, | |
Fernando Perez
|
r5785 | "source": [ | |
MinRK
|
r7739 | "## Adding IPython display support to existing objects\n", | |
"\n", | |||
"When you are directly writing your own classes, you can adapt them for display in IPython by \n", | |||
"following the above example. But in practice, we often need to work with existing code we\n", | |||
"can't modify. \n", | |||
"\n", | |||
"We now illustrate how to add these kinds of extended display capabilities to existing objects.\n", | |||
"We will use the numpy polynomials and change their default representation to be a formatted\n", | |||
"LaTeX expression.\n", | |||
"\n", | |||
Fernando Perez
|
r5785 | "First, consider how a numpy polynomial object renders by default:" | |
] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
MinRK
|
r7739 | "p = np.polynomial.Polynomial([1,2,3], [-10, 10])\n", | |
Fernando Perez
|
r5785 | "p" | |
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "markdown", | |
MinRK
|
r7739 | "metadata": {}, | |
Fernando Perez
|
r5785 | "source": [ | |
"Next, we define a function that pretty-prints a polynomial as a LaTeX string:" | |||
] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
MinRK
|
r7739 | "collapsed": false, | |
Fernando Perez
|
r5785 | "input": [ | |
MinRK
|
r7739 | "def poly2latex(p):\n", | |
" terms = ['%.2g' % p.coef[0]]\n", | |||
" if len(p) > 1:\n", | |||
" term = 'x'\n", | |||
" c = p.coef[1]\n", | |||
" if c!=1:\n", | |||
" term = ('%.2g ' % c) + term\n", | |||
" terms.append(term)\n", | |||
" if len(p) > 2:\n", | |||
" for i in range(2, len(p)):\n", | |||
" term = 'x^%d' % i\n", | |||
" c = p.coef[i]\n", | |||
" if c!=1:\n", | |||
" term = ('%.2g ' % c) + term\n", | |||
" terms.append(term)\n", | |||
" px = '$P(x)=%s$' % '+'.join(terms)\n", | |||
" dom = r', domain: $[%.2g,\\ %.2g]$' % tuple(p.domain)\n", | |||
Fernando Perez
|
r5785 | " return px+dom" | |
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "markdown", | |
MinRK
|
r7739 | "metadata": {}, | |
Fernando Perez
|
r5785 | "source": [ | |
"This produces, on our polynomial ``p``, the following:" | |||
] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
"poly2latex(p)" | |||
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
MinRK
|
r7739 | "from IPython.display import Latex\n", | |
"Latex(poly2latex(p))" | |||
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "markdown", | |
MinRK
|
r7739 | "metadata": {}, | |
Fernando Perez
|
r5785 | "source": [ | |
MinRK
|
r7739 | "But we can configure IPython to do this automatically for us as follows. We hook into the\n", | |
"IPython display system and instruct it to use ``poly2latex`` for the latex mimetype, when\n", | |||
"encountering objects of the ``Polynomial`` type defined in the\n", | |||
Fernando Perez
|
r5785 | "``numpy.polynomial.polynomial`` module:" | |
] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
MinRK
|
r7739 | "collapsed": false, | |
Fernando Perez
|
r5785 | "input": [ | |
MinRK
|
r7739 | "ip = get_ipython()\n", | |
"latex_formatter = ip.display_formatter.formatters['text/latex']\n", | |||
"latex_formatter.for_type_by_name('numpy.polynomial.polynomial',\n", | |||
Fernando Perez
|
r5785 | " 'Polynomial', poly2latex)" | |
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "markdown", | |
MinRK
|
r7739 | "metadata": {}, | |
Fernando Perez
|
r5785 | "source": [ | |
MinRK
|
r7739 | "For more examples on how to use the above system, and how to bundle similar print functions\n", | |
"into a convenient IPython extension, see the ``IPython/extensions/sympyprinting.py`` file. \n", | |||
"The machinery that defines the display system is in the ``display.py`` and ``displaypub.py`` \n", | |||
"files in ``IPython/core``.\n", | |||
"\n", | |||
"Once our special printer has been loaded, all polynomials will be represented by their \n", | |||
Fernando Perez
|
r5785 | "mathematical form instead:" | |
] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
"p" | |||
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Brian Granger
|
r6035 | }, | |
Fernando Perez
|
r5785 | { | |
Brian Granger
|
r6035 | "cell_type": "code", | |
"collapsed": false, | |||
Fernando Perez
|
r5785 | "input": [ | |
MinRK
|
r7739 | "p2 = np.polynomial.Polynomial([-20, 71, -15, 1])\n", | |
Fernando Perez
|
r5785 | "p2" | |
Brian Granger
|
r6035 | ], | |
"language": "python", | |||
MinRK
|
r7739 | "metadata": {}, | |
"outputs": [] | |||
Fernando Perez
|
r5785 | } | |
MinRK
|
r7739 | ], | |
"metadata": {} | |||
Fernando Perez
|
r5785 | } | |
] | |||
Fernando Perez
|
r4776 | } |