The rich display protocol

Jupyter · Advanced · 12 min

Library authors and power users can give any object a table, chart or custom view in Jupyter with a single method.

When a notebook displays an object, IPython asks it for representations in several MIME types and sends all of them to the front end, which chooses the richest it can render. An object opts in by defining methods:

  • _repr_html_() returns HTML (pandas tables use this);
  • _repr_markdown_(), _repr_latex_(), _repr_svg_(), _repr_png_(), _repr_json_();
  • _repr_mimebundle_(include=None, exclude=None) returns a dict mapping any MIME type to data, for full control, including custom types such as application/vnd.plotly.v1+json. Prefer either it or the individual methods, not both, so the result does not depend on how IPython combines them.

__repr__ always provides the text/plain fallback, used in terminals and by front ends that understand nothing richer. The whole bundle is saved in the notebook, so the same output can appear as HTML in JupyterLab and as text in a terminal viewer.

Return None from a _repr_*_ method to say "not available for this object", for example when it is too large to render.

Display IDs let you update an existing output instead of appending new ones:

h = display("starting...", display_id=True)
h.update("50% done")

Every output with that display ID updates, which is perfect for progress messages or live-updating results.

For types you do not own, register a formatter: get_ipython().display_formatter.formatters["text/html"].for_type(MyType, func).

Example

from IPython import get_ipython      # run this in a notebook kernel
from IPython.display import display

class Money:
    def __init__(self, amount, currency):
        self.amount, self.currency = amount, currency
    def __repr__(self):
        return f"Money({self.amount}, {self.currency!r})"
    def _repr_html_(self):
        color = "green" if self.amount >= 0 else "red"
        return f"<b style='color:{color}'>{self.amount:,.2f} {self.currency}</b>"
    def _repr_markdown_(self):
        return f"**{self.amount:,.2f} {self.currency}**"

m = Money(-1234.5, "EUR")
data, meta = get_ipython().display_formatter.format(m)
print(sorted(data))   # ['text/html', 'text/markdown', 'text/plain']
print(data["text/plain"])            # Money(-1234.5, 'EUR')

h = display("step 1 of 3", display_id=True)   # in a notebook: one output
h.update("step 3 of 3")                        # ...updated in place

Remember

  • IPython collects a bundle of MIME representations; the front end picks the richest
  • Define _repr_html_ or _repr_mimebundle_ to give objects a rich view
  • display(..., display_id=True) returns a handle whose update() edits output in place

Watch out for

  • Returning enormous HTML from _repr_html_, which slows the browser and bloats saved notebooks.

Check yourself

  1. Your object defines both `__repr__` and `_repr_html_`. What does JupyterLab show?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: ipython.readthedocs.io/en/stable/config/integrating.html