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