diff --git a/IPython/core/display.py b/IPython/core/display.py index 2c9cc01..0915ede 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -164,20 +164,13 @@ def display(*objs, **kwargs): format = InteractiveShell.instance().display_formatter.format for obj in objs: - - # If _ipython_display_ is defined, use that to display this object. - display_method = _safe_get_formatter_method(obj, '_ipython_display_') - if display_method is not None: - try: - display_method(**kwargs) - except NotImplementedError: - pass - else: - continue if raw: publish_display_data(data=obj, metadata=metadata) else: format_dict, md_dict = format(obj, include=include, exclude=exclude) + if not format_dict: + # nothing to display (e.g. _ipython_display_ took over) + continue if metadata: # kwarg-specified metadata gets precedence _merge(md_dict, metadata) diff --git a/IPython/core/displayhook.py b/IPython/core/displayhook.py index 544867a..9c1ce82 100644 --- a/IPython/core/displayhook.py +++ b/IPython/core/displayhook.py @@ -221,20 +221,13 @@ class DisplayHook(Configurable): """ self.check_for_underscore() if result is not None and not self.quiet(): - # If _ipython_display_ is defined, use that to display this object. - display_method = _safe_get_formatter_method(result, '_ipython_display_') - if display_method is not None: - try: - return display_method() - except NotImplementedError: - pass - self.start_displayhook() self.write_output_prompt() format_dict, md_dict = self.compute_format_data(result) - self.write_format_data(format_dict, md_dict) self.update_user_ns(result) - self.log_output(format_dict) + if format_dict: + self.write_format_data(format_dict, md_dict) + self.log_output(format_dict) self.finish_displayhook() def cull_cache(self): diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py index 6ceb23c..710a221 100644 --- a/IPython/core/formatters.py +++ b/IPython/core/formatters.py @@ -14,7 +14,6 @@ import abc import inspect import sys import traceback -import types import warnings from IPython.external.decorator import decorator @@ -24,6 +23,7 @@ from IPython.core.getipython import get_ipython from IPython.lib import pretty from IPython.utils.traitlets import ( Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List, + ForwardDeclaredInstance, ) from IPython.utils.py3compat import ( unicode_to_str, with_metaclass, PY3, string_types, unicode_type, @@ -89,6 +89,10 @@ class DisplayFormatter(Configurable): else: formatter.enabled = False + ipython_display_formatter = ForwardDeclaredInstance('FormatterABC') + def _ipython_display_formatter_default(self): + return IPythonDisplayFormatter(parent=self) + # A dict of formatter whose keys are format types (MIME types) and whose # values are subclasses of BaseFormatter. formatters = Dict() @@ -158,7 +162,11 @@ class DisplayFormatter(Configurable): """ format_dict = {} md_dict = {} - + + if self.ipython_display_formatter(obj): + # object handled itself, don't proceed + return {}, {} + for format_type, formatter in self.formatters.items(): if include and format_type not in include: continue @@ -831,6 +839,40 @@ class PDFFormatter(BaseFormatter): _return_type = (bytes, unicode_type) +class IPythonDisplayFormatter(BaseFormatter): + """A Formatter for objects that know how to display themselves. + + To define the callables that compute the representation of your + objects, define a :meth:`_ipython_display_` method or use the :meth:`for_type` + or :meth:`for_type_by_name` methods to register functions that handle + this. Unlike mime-type displays, this method should not return anything, + instead calling any appropriate display methods itself. + + This display formatter has highest priority. + If it fires, no other display formatter will be called. + """ + print_method = ObjectName('_ipython_display_') + _return_type = (type(None), bool) + + + @warn_format_error + def __call__(self, obj): + """Compute the format for an object.""" + if self.enabled: + # lookup registered printer + try: + printer = self.lookup(obj) + except KeyError: + pass + else: + printer(obj) + return True + # Finally look for special method names + method = _safe_get_formatter_method(obj, self.print_method) + if method is not None: + method() + return True + FormatterABC.register(BaseFormatter) FormatterABC.register(PlainTextFormatter) @@ -843,6 +885,7 @@ FormatterABC.register(JPEGFormatter) FormatterABC.register(LatexFormatter) FormatterABC.register(JSONFormatter) FormatterABC.register(JavascriptFormatter) +FormatterABC.register(IPythonDisplayFormatter) def format_display_data(obj, include=None, exclude=None): diff --git a/IPython/core/tests/test_formatters.py b/IPython/core/tests/test_formatters.py index 5322bb3..7956efc 100644 --- a/IPython/core/tests/test_formatters.py +++ b/IPython/core/tests/test_formatters.py @@ -10,7 +10,8 @@ import nose.tools as nt from IPython.config import Config from IPython.core.formatters import ( - PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key + PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key, + DisplayFormatter, ) from IPython.utils.io import capture_output @@ -387,3 +388,33 @@ def test_pretty_max_seq_length(): text = f(list(range(1024))) lines = text.splitlines() nt.assert_equal(len(lines), 1024) + + +def test_ipython_display_formatter(): + """Objects with _ipython_display_ defined bypass other formatters""" + f = get_ipython().display_formatter + catcher = [] + class SelfDisplaying(object): + def _ipython_display_(self): + catcher.append(self) + + class NotSelfDisplaying(object): + def __repr__(self): + return "NotSelfDisplaying" + + def _ipython_display_(self): + raise NotImplementedError + + yes = SelfDisplaying() + no = NotSelfDisplaying() + + d, md = f.format(no) + nt.assert_equal(d, {'text/plain': repr(no)}) + nt.assert_equal(md, {}) + nt.assert_equal(catcher, []) + + d, md = f.format(yes) + nt.assert_equal(d, {}) + nt.assert_equal(md, {}) + nt.assert_equal(catcher, [yes]) +