diff -r 47bf09270027 Lib/test/test_traceback.py --- a/Lib/test/test_traceback.py Sun Dec 11 14:35:07 2016 -0800 +++ b/Lib/test/test_traceback.py Mon Dec 12 23:59:02 2016 -0800 @@ -19,6 +19,10 @@ test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next']) +class UnhashableError(Exception): + __hash__ = None + + class TracebackCases(unittest.TestCase): # For now, a very minimal set of tests. I want to be sure that # formatting of SyntaxErrors works based on changes for 2.1. @@ -463,6 +467,29 @@ err = self.get_report(Exception('')) self.assertIn('Exception\n', err) + def test_unhashable_exception(self): + # http://bugs.python.org/issue28603 + report = self.get_report(UnhashableError()) + self.assertIn('UnhashableError', report) + + try: + raise UnhashableError('err1') from UnhashableError('err2') + except UnhashableError as e: + report = self.get_report(e) + self.assertIn('UnhashableError', report) + self.assertIn('err1', report) + self.assertIn('err2', report) + + try: + try: + raise UnhashableError('err1') + except UnhashableError: + raise UnhashableError('err2') + except UnhashableError as e: + report = self.get_report(e) + self.assertIn('UnhashableError', report) + self.assertIn('err2', report) + class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase): # diff -r 47bf09270027 Lib/traceback.py --- a/Lib/traceback.py Sun Dec 11 14:35:07 2016 -0800 +++ b/Lib/traceback.py Mon Dec 12 23:59:02 2016 -0800 @@ -436,11 +436,15 @@ # Handle loops in __cause__ or __context__. if _seen is None: _seen = set() - _seen.add(exc_value) + try: + _seen.add(exc_value) + except TypeError: + # see issue 28603 + pass # Gracefully handle (the way Python 2.4 and earlier did) the case of # being called with no type or value (None, None, None). if (exc_value and exc_value.__cause__ is not None - and exc_value.__cause__ not in _seen): + and not self._safe_in(exc_value.__cause__, _seen)): cause = TracebackException( type(exc_value.__cause__), exc_value.__cause__, @@ -452,7 +456,7 @@ else: cause = None if (exc_value and exc_value.__context__ is not None - and exc_value.__context__ not in _seen): + and not self._safe_in(exc_value.__context__, _seen)): context = TracebackException( type(exc_value.__context__), exc_value.__context__, @@ -486,6 +490,14 @@ if lookup_lines: self._load_lines() + def _safe_in(self, element, sequence): + try: + return element in sequence + except TypeError: + # assume it's there if the element is not hashable, so we don't + # continue traversing the chain + return True + @classmethod def from_exception(self, exc, *args, **kwargs): """Create a TracebackException from an exception."""