{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Defining Custom Display Logic for Your Own Objects" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overview" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In Python, objects can declare their textual representation using the `__repr__` method. IPython expands on this idea and allows objects to declare other, richer representations including:\n", "\n", "* HTML\n", "* JSON\n", "* PNG\n", "* JPEG\n", "* SVG\n", "* LaTeX\n", "\n", "This Notebook shows how you can add custom display logic to your own classes, so that they can be displayed using these rich representations. There are two ways of accomplishing this:\n", "\n", "1. Implementing special display methods such as `_repr_html_`.\n", "2. Registering a display function for a particular type.\n", "\n", "In this Notebook we show how both approaches work." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Before we get started, we will import the various display functions for displaying the different formats we will create." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from IPython.display import display\n", "from IPython.display import (\n", " display_html, display_jpeg, display_png,\n", " display_javascript, display_svg, display_latex\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Implementing special display methods" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The main idea of the first approach is that you have to implement special display methods, one for each representation you want to use. Here is a list of the names of the special methods and the values they must return:\n", "\n", "* `_repr_html_`: return raw HTML as a string\n", "* `_repr_json_`: return raw JSON as a string\n", "* `_repr_jpeg_`: return raw JPEG data\n", "* `_repr_png_`: return raw PNG data\n", "* `_repr_svg_`: return raw SVG data as a string\n", "* `_repr_latex_`: return LaTeX commands in a string surrounded by \"$\"." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Model Citizen: pandas" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A prominent example of a package that has IPython-aware rich representations of its objects is [pandas](http://pandas.pydata.org/).\n", "\n", "A pandas DataFrame has a rich HTML table representation,\n", "using `_repr_html_`.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import pandas" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%%writefile data.csv\n", "Date,Open,High,Low,Close,Volume,Adj Close\n", "2012-06-01,569.16,590.00,548.50,584.00,14077000,581.50\n", "2012-05-01,584.90,596.76,522.18,577.73,18827900,575.26\n", "2012-04-02,601.83,644.00,555.00,583.98,28759100,581.48\n", "2012-03-01,548.17,621.45,516.22,599.55,26486000,596.99\n", "2012-02-01,458.41,547.61,453.98,542.44,22001000,540.12\n", "2012-01-03,409.40,458.24,409.00,456.48,12949100,454.53\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "df = pandas.read_csv(\"data.csv\")\n", "pandas.set_option('display.notebook_repr_html', False)\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "rich HTML can be activated via `pandas.set_option`." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "pandas.set_option('display.notebook_repr_html', True)\n", "df" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "lines = df._repr_html_().splitlines()\n", "print(\"\\n\".join(lines[:20]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Write a simple `Circle` Python class. Don't even worry about properties such as radius, position, colors, etc. To help you out use the following representations (remember to wrap them in Python strings):\n", "\n", "For HTML:\n", "\n", " ○\n", "\n", "For SVG:\n", "\n", " \n", " \n", " \n", "\n", "For LaTeX (wrap with `$` and use a raw Python string):\n", "\n", " \\bigcirc\n", "\n", "After you write the class, create an instance and then use `display_html`, `display_svg` and `display_latex` to display those representations.\n", "\n", "Tips : you can slightly tweek the representation to know from which `_repr_*_` method it came from. \n", "For example in my solution the svg representation is blue, and the HTML one show \"`HTML`\" between brackets." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Solution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is my simple `Circle` class:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%load mycircle.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now create an instance and use the display methods:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "c = MyCircle()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "display_html(c)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "display_svg(c)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "display_latex(c)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "display_javascript(c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Adding IPython display support to existing objects" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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. To continue with our example above, we will add a PNG representation to our `Circle` class using Matplotlib." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Model citizen: sympy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[SymPy](http://sympy.org) is another model citizen that defines rich representations of its object.\n", "Unlike pandas above, sympy registers display formatters via IPython's display formatter API, rather than declaring `_repr_mime_` methods." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from sympy import Rational, pi, exp, I, symbols\n", "x, y, z = symbols(\"x y z\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "r = Rational(3,2)*pi + exp(I*x) / (x**2 + y)\n", "r" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "SymPy provides an `init_printing` function that sets up advanced formatting (including $\\LaTeX$\n", "representations) of its objects:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from sympy.interactive.printing import init_printing\n", "init_printing(use_latex=False) # no LaTeX enabled, we get unicode printing\n", "r" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Now activate LaTeX\n", "init_printing( use_latex=True )\n", "r" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To add a display method to an existing class, we must use IPython's display formatter API. Here we show all of the available formatters:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "ip = get_ipython()\n", "ip.display_formatter.formatters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's grab the PNG formatter:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "png_f = ip.display_formatter.formatters['image/png']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will use the `for_type` method to register our display function." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "png_f.for_type?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As the docstring describes, we need to define a function the takes the object as a parameter and returns the raw PNG data." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%matplotlib inline\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class AnotherCircle(object): \n", " def __repr__(self):\n", " return \"\"\n", " \n", "c = AnotherCircle()\n", "c" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from IPython.core.pylabtools import print_figure\n", "\n", "def png_circle(circle):\n", " \"\"\"Render AnotherCircle to png data using matplotlib\"\"\"\n", " fig, ax = plt.subplots()\n", " patch = plt.Circle((0,0), radius=1.0, fc='r')\n", " ax.add_patch(patch)\n", " plt.axis('scaled')\n", " data = print_figure(fig, 'png')\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" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we register the display function for the type:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "png_f.for_type(AnotherCircle, png_circle)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now all `Circle` instances have PNG representations!" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "c2 = AnotherCircle()\n", "c2" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "display_png(c2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise\n", "\n", "Register for the `AnotherCircle` class a LaTeX formatter that uses the same value as the one in the `Circle` class above, but without adding new methods to `AnotherCircle`. That is, use the `text/latex` formatter similarly to how we used the `image/png` one." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Solution" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%load mycircle2.py" ] } ], "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.2" }, "widgets": { "state": {}, "version": "1.1.1" } }, "nbformat": 4, "nbformat_minor": 1 }