Repository: ipython/ipython

Issue:
IPython swallows exceptions in repr
### IPython console behavior

``` Python
In [1]: class Foo(object):
   ...:     def __repr__(self):
   ...:         raise Exception()
   ...:     

In [2]: Foo()
Out[2]: <repr(<__main__.Foo at 0x7f50f6bb69d0>) failed: Exception: >
```
### Normal Python repl behavior

``` Python
>>> class Foo(object):
...     def __repr__(self):
...         raise Exception()
... 
>>> Foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __repr__
Exception
```

In some cases (particularly the case that I'm dealing with), it'd be nice for IPython to allow this exception to bubble up to the user, rather than just printing it out.



Discussion:
Yeah, it would be much nicer. I didn't notice this strange behavior before, but was able to reproduce it in IPython 3.

I did some work in exception formatting code in ipython and I think that I can fix this issue too.

Is there anyone thinking that this particular behavior should stay as it is now?

I think this is actually a recent fix for protecting IPython from objects that are severely broken. If you call `repr(Foo())`, the exception will just raise. It's just a question of what the IPython display for an object that has no working `__repr__` should be. That code can be changed to include the traceback, but I'm not sure it can be changed to allow the exception to just raise.

Printing out the traceback seems like a better visual cue that an error has occured than the current solution.  Some users visually parse the current output as the standard output for objects without defined `__repr__`.


Resolving patch:
diff --git a/IPython/core/displayhook.py b/IPython/core/displayhook.py
--- a/IPython/core/displayhook.py
+++ b/IPython/core/displayhook.py
@@ -2,25 +2,11 @@
 """Displayhook for IPython.
 
 This defines a callable class that IPython uses for `sys.displayhook`.
-
-Authors:
-
-* Fernando Perez
-* Brian Granger
-* Robert Kern
 """
 
-#-----------------------------------------------------------------------------
-#       Copyright (C) 2008-2011 The IPython Development Team
-#       Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
-#
-#  Distributed under the terms of the BSD License.  The full license is in
-#  the file COPYING, distributed as part of this software.
-#-----------------------------------------------------------------------------
-
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
 from __future__ import print_function
 
 import sys
@@ -32,10 +18,6 @@
 from IPython.utils.traitlets import Instance
 from IPython.utils.warn import warn
 
-#-----------------------------------------------------------------------------
-# Main displayhook class
-#-----------------------------------------------------------------------------
-
 # TODO: Move the various attributes (cache_size, [others now moved]). Some
 # of these are also attributes of InteractiveShell. They should be on ONE object
 # only and the other objects should ask that one object for their values.
@@ -168,6 +150,9 @@ def write_format_data(self, format_dict, md_dict=None):
         md_dict : dict (optional)
             The metadata dict to be associated with the display data.
         """
+        if 'text/plain' not in format_dict:
+            # nothing to do
+            return
         # We want to print because we want to always make sure we have a
         # newline, even if all the prompt separators are ''. This is the
         # standard IPython behavior.
@@ -221,6 +206,9 @@ def update_user_ns(self, result):
 
     def log_output(self, format_dict):
         """Log the output."""
+        if 'text/plain' not in format_dict:
+            # nothing to do
+            return
         if self.shell.logger.log_output:
             self.shell.logger.log_write(format_dict['text/plain'], 'output')
         self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py
--- a/IPython/core/formatters.py
+++ b/IPython/core/formatters.py
@@ -5,25 +5,11 @@
 
 .. inheritance-diagram:: IPython.core.formatters
    :parts: 3
-
-Authors:
-
-* Robert Kern
-* Brian Granger
 """
-#-----------------------------------------------------------------------------
-# Copyright (C) 2010-2011, IPython Development Team.
-#
-# Distributed under the terms of the Modified BSD License.
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
 
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
 
-# Stdlib imports
 import abc
 import inspect
 import sys
@@ -32,8 +18,8 @@
 
 from IPython.external.decorator import decorator
 
-# Our own imports
 from IPython.config.configurable import Configurable
+from IPython.core.getipython import get_ipython
 from IPython.lib import pretty
 from IPython.utils.traitlets import (
     Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
@@ -223,6 +209,18 @@ def format_types(self):
 # Formatters for specific format types (text, html, svg, etc.)
 #-----------------------------------------------------------------------------
 
+
+def _safe_repr(obj):
+    """Try to return a repr of an object
+
+    always returns a string, at least.
+    """
+    try:
+        return repr(obj)
+    except Exception as e:
+        return "un-repr-able object (%r)" % e
+
+
 class FormatterWarning(UserWarning):
     """Warning class for errors in formatters"""
 
@@ -234,10 +232,13 @@ def warn_format_error(method, self, *args, **kwargs):
     except NotImplementedError as e:
         # don't warn on NotImplementedErrors
         return None
-    except Exception as e:
-        warnings.warn("Exception in %s formatter: %s" % (self.format_type, e),
-            FormatterWarning,
-        )
+    except Exception:
+        exc_info = sys.exc_info()
+        ip = get_ipython()
+        if ip is not None:
+            ip.showtraceback(exc_info)
+        else:
+            traceback.print_exception(*exc_info)
         return None
     if r is None or isinstance(r, self._return_type) or \
         (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
@@ -245,7 +246,7 @@ def warn_format_error(method, self, *args, **kwargs):
     else:
         warnings.warn(
             "%s formatter returned invalid type %s (expected %s) for object: %s" % \
-            (self.format_type, type(r), self._return_type, pretty._safe_repr(args[0])),
+            (self.format_type, type(r), self._return_type, _safe_repr(args[0])),
             FormatterWarning
         )
 
@@ -672,7 +673,7 @@ def _deferred_printers_default(self):
     def __call__(self, obj):
         """Compute the pretty representation of the object."""
         if not self.pprint:
-            return pretty._safe_repr(obj)
+            return repr(obj)
         else:
             # This uses use StringIO, as cStringIO doesn't handle unicode.
             stream = StringIO()
diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py
--- a/IPython/lib/pretty.py
+++ b/IPython/lib/pretty.py
@@ -125,49 +125,6 @@ def _repr_pretty_(self, p, cycle):
 
 _re_pattern_type = type(re.compile(''))
 
-def _failed_repr(obj, e):
-    """Render a failed repr, including the exception.
-    
-    Tries to get exception and type info
-    """
-    # get exception name
-    if e.__class__.__module__ in ('exceptions', 'builtins'):
-        ename = e.__class__.__name__
-    else:
-        ename = '{}.{}'.format(
-            e.__class__.__module__,
-            e.__class__.__name__,
-        )
-    # and exception string, which sometimes fails
-    # (usually due to unicode error message)
-    try:
-        estr = str(e)
-    except Exception:
-        estr = "unknown"
-    
-    # and class name
-    try:
-        klass = _safe_getattr(obj, '__class__', None) or type(obj)
-        mod = _safe_getattr(klass, '__module__', None)
-        if mod in (None, '__builtin__', 'builtins', 'exceptions'):
-            classname = klass.__name__
-        else:
-            classname = mod + '.' + klass.__name__
-    except Exception:
-        # this may be paranoid, but we already know repr is broken
-        classname = "unknown type"
-    
-    # the informative repr
-    return "<repr(<{} at 0x{:x}>) failed: {}: {}>".format(
-        classname, id(obj), ename, estr,
-    )
-
-def _safe_repr(obj):
-    """Don't assume repr is not broken."""
-    try:
-        return repr(obj)
-    except Exception as e:
-        return _failed_repr(obj, e)
 
 def _safe_getattr(obj, attr, default=None):
     """Safe version of getattr.
@@ -560,7 +517,7 @@ def _default_pprint(obj, p, cycle):
     klass = _safe_getattr(obj, '__class__', None) or type(obj)
     if _safe_getattr(klass, '__repr__', None) not in _baseclass_reprs:
         # A user-provided repr. Find newlines and replace them with p.break_()
-        output = _safe_repr(obj)
+        output = repr(obj)
         for idx,output_line in enumerate(output.splitlines()):
             if idx:
                 p.break_()
@@ -736,7 +693,7 @@ def _type_pprint(obj, p, cycle):
 
 def _repr_pprint(obj, p, cycle):
     """A pprint that just redirects to the normal repr function."""
-    p.text(_safe_repr(obj))
+    p.text(repr(obj))
 
 
 def _function_pprint(obj, p, cycle):


Test patch:
diff --git a/IPython/core/tests/test_formatters.py b/IPython/core/tests/test_formatters.py
--- a/IPython/core/tests/test_formatters.py
+++ b/IPython/core/tests/test_formatters.py
@@ -25,6 +25,10 @@ def __repr__(self):
 class C:
     pass
 
+class BadRepr(object):
+    def __repr__(self):
+        raise ValueError("bad repr")
+
 class BadPretty(object):
     _repr_pretty_ = None
 
@@ -234,30 +238,30 @@ def test_pop_string():
     nt.assert_is(f.pop(type_str, None), None)
     
 
-def test_warn_error_method():
+def test_error_method():
     f = HTMLFormatter()
     class BadHTML(object):
         def _repr_html_(self):
-            return 1/0
+            raise ValueError("Bad HTML")
     bad = BadHTML()
     with capture_output() as captured:
         result = f(bad)
     nt.assert_is(result, None)
-    nt.assert_in("FormatterWarning", captured.stderr)
-    nt.assert_in("text/html", captured.stderr)
-    nt.assert_in("zero", captured.stderr)
+    nt.assert_in("Traceback", captured.stdout)
+    nt.assert_in("Bad HTML", captured.stdout)
+    nt.assert_in("_repr_html_", captured.stdout)
 
 def test_nowarn_notimplemented():
     f = HTMLFormatter()
     class HTMLNotImplemented(object):
         def _repr_html_(self):
             raise NotImplementedError
-            return 1/0
     h = HTMLNotImplemented()
     with capture_output() as captured:
         result = f(h)
     nt.assert_is(result, None)
-    nt.assert_not_in("FormatterWarning", captured.stderr)
+    nt.assert_equal("", captured.stderr)
+    nt.assert_equal("", captured.stdout)
 
 def test_warn_error_for_type():
     f = HTMLFormatter()
@@ -265,11 +269,11 @@ def test_warn_error_for_type():
     with capture_output() as captured:
         result = f(5)
     nt.assert_is(result, None)
-    nt.assert_in("FormatterWarning", captured.stderr)
-    nt.assert_in("text/html", captured.stderr)
-    nt.assert_in("name_error", captured.stderr)
+    nt.assert_in("Traceback", captured.stdout)
+    nt.assert_in("NameError", captured.stdout)
+    nt.assert_in("name_error", captured.stdout)
 
-def test_warn_error_pretty_method():
+def test_error_pretty_method():
     f = PlainTextFormatter()
     class BadPretty(object):
         def _repr_pretty_(self):
@@ -278,9 +282,23 @@ def _repr_pretty_(self):
     with capture_output() as captured:
         result = f(bad)
     nt.assert_is(result, None)
-    nt.assert_in("FormatterWarning", captured.stderr)
-    nt.assert_in("text/plain", captured.stderr)
-    nt.assert_in("argument", captured.stderr)
+    nt.assert_in("Traceback", captured.stdout)
+    nt.assert_in("_repr_pretty_", captured.stdout)
+    nt.assert_in("given", captured.stdout)
+    nt.assert_in("argument", captured.stdout)
+
+
+def test_bad_repr_traceback():
+    f = PlainTextFormatter()
+    bad = BadRepr()
+    with capture_output() as captured:
+        result = f(bad)
+    # catches error, returns None
+    nt.assert_is(result, None)
+    nt.assert_in("Traceback", captured.stdout)
+    nt.assert_in("__repr__", captured.stdout)
+    nt.assert_in("ValueError", captured.stdout)
+
 
 class MakePDF(object):
     def _repr_pdf_(self):
diff --git a/IPython/lib/tests/test_pretty.py b/IPython/lib/tests/test_pretty.py
--- a/IPython/lib/tests/test_pretty.py
+++ b/IPython/lib/tests/test_pretty.py
@@ -1,16 +1,8 @@
-"""Tests for IPython.lib.pretty.
-"""
-#-----------------------------------------------------------------------------
-# Copyright (c) 2011, the IPython Development Team.
-#
+"""Tests for IPython.lib.pretty."""
+
+# Copyright (c) IPython Development Team.
 # Distributed under the terms of the Modified BSD License.
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
 
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
 from __future__ import print_function
 
 # Third-party imports
@@ -20,9 +12,6 @@
 from IPython.lib import pretty
 from IPython.testing.decorators import skip_without
 
-#-----------------------------------------------------------------------------
-# Classes and functions
-#-----------------------------------------------------------------------------
 
 class MyList(object):
     def __init__(self, content):
@@ -161,11 +150,9 @@ def test_pprint_break_repr():
     nt.assert_equal(output, expected)
 
 def test_bad_repr():
-    """Don't raise, even when repr fails"""
-    output = pretty.pretty(BadRepr())
-    nt.assert_in("failed", output)
-    nt.assert_in("at 0x", output)
-    nt.assert_in("test_pretty", output)
+    """Don't catch bad repr errors"""
+    with nt.assert_raises(ZeroDivisionError):
+        output = pretty.pretty(BadRepr())
 
 class BadException(Exception):
     def __str__(self):
@@ -181,10 +168,8 @@ def __repr__(self):
         raise BadException()
 
 def test_really_bad_repr():
-    output = pretty.pretty(ReallyBadRepr())
-    nt.assert_in("failed", output)
-    nt.assert_in("BadException: unknown", output)
-    nt.assert_in("unknown type", output)
+    with nt.assert_raises(BadException):
+        output = pretty.pretty(ReallyBadRepr())
 
 
 class SA(object):