display_protocol.ipynb
377 lines
| 12.3 KiB
| text/plain
|
TextLexer
Fernando Perez
|
r4776 | { | |
Fernando Perez
|
r5785 | "metadata": { | |
"name": "display_protocol" | |||
}, | |||
"nbformat": 2, | |||
"worksheets": [ | |||
{ | |||
"cells": [ | |||
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"# 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.", | |||
"", | |||
"The next cell defines the Gaussian class:" | |||
] | |||
MinRK
|
r5279 | }, | |
Fernando Perez
|
r5785 | { | |
"cell_type": "code", | |||
Fernando Perez
|
r6326 | "collapsed": false, | |
Fernando Perez
|
r5785 | "input": [ | |
Fernando Perez
|
r6326 | "from IPython.core.pylabtools import print_figure", | |
Fernando Perez
|
r5785 | "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)", | |||
" return Image(data, embed=True)" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
"prompt_number": 1 | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"Now, we create an instance of the Gaussian distribution, whose default representation will be its LaTeX form:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"x = Gaussian()", | |||
"x" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
"prompt_number": 2 | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"We can view the data in png or svg formats:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"x.png" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
"prompt_number": 3 | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"x.svg" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
"prompt_number": 4 | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"Since IPython only displays by default as an ``Out[]`` cell the result of the last computation, we can use the", | |||
"``display()`` function to show more than one representation in a single cell:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"display(x.png)", | |||
"display(x.svg)" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
"prompt_number": 5 | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"Now let's create a new Gaussian with different parameters" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"x2 = Gaussian(0.5, 0.2, 2000)", | |||
"x2" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
"prompt_number": 6 | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"We can easily compare them by displaying their histograms" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"display(x.hist)", | |||
"display(x2.hist)" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
"prompt_number": 7 | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"## 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.", | |||
"", | |||
"First, consider how a numpy polynomial object renders by default:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"p = np.polynomial.Polynomial([1,2,3], [-10, 10])", | |||
"p" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
"prompt_number": 8 | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"Next, we define a function that pretty-prints a polynomial as a LaTeX string:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": true, | |||
"input": [ | |||
"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)", | |||
" return px+dom" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
Fernando Perez
|
r6326 | "prompt_number": 9 | |
Fernando Perez
|
r5785 | }, | |
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"This produces, on our polynomial ``p``, the following:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"poly2latex(p)" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
Fernando Perez
|
r6326 | "prompt_number": 10 | |
Fernando Perez
|
r5785 | }, | |
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"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", | |||
"must be properly wrapped into a Math object:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"from IPython.core.display import Math", | |||
"Math(poly2latex(p))" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
Fernando Perez
|
r6326 | "prompt_number": 11 | |
Fernando Perez
|
r5785 | }, | |
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"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", | |||
"``numpy.polynomial.polynomial`` module:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": true, | |||
"input": [ | |||
"ip = get_ipython()", | |||
"latex_formatter = ip.display_formatter.formatters['text/latex']", | |||
"latex_formatter.for_type_by_name('numpy.polynomial.polynomial',", | |||
" 'Polynomial', poly2latex)" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
Fernando Perez
|
r6326 | "prompt_number": 12 | |
Fernando Perez
|
r5785 | }, | |
{ | |||
"cell_type": "markdown", | |||
"source": [ | |||
"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 ", | |||
"mathematical form instead:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"p" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
Fernando Perez
|
r6326 | "prompt_number": 13 | |
Fernando Perez
|
r5785 | }, | |
{ | |||
"cell_type": "code", | |||
"collapsed": false, | |||
"input": [ | |||
"p2 = np.polynomial.Polynomial([-20, 71, -15, 1])", | |||
"p2" | |||
], | |||
"language": "python", | |||
"outputs": [], | |||
Fernando Perez
|
r6326 | "prompt_number": 14 | |
Fernando Perez
|
r5785 | }, | |
{ | |||
"cell_type": "code", | |||
"collapsed": true, | |||
"input": [], | |||
"language": "python", | |||
"outputs": [], | |||
"prompt_number": 14 | |||
} | |||
] | |||
} | |||
] | |||
Fernando Perez
|
r4776 | } |