@@ -481,39 +481,10 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
481
481
# permit backwards compat with the existing API, otherwise we
482
482
# need stub thunk objects just to glue it together.
483
483
# Handle loops in __cause__ or __context__.
484
+ is_recursive_call = _seen is not None
484
485
if _seen is None :
485
486
_seen = set ()
486
487
_seen .add (id (exc_value ))
487
- # Gracefully handle (the way Python 2.4 and earlier did) the case of
488
- # being called with no type or value (None, None, None).
489
- if (exc_value and exc_value .__cause__ is not None
490
- and id (exc_value .__cause__ ) not in _seen ):
491
- cause = TracebackException (
492
- type (exc_value .__cause__ ),
493
- exc_value .__cause__ ,
494
- exc_value .__cause__ .__traceback__ ,
495
- limit = limit ,
496
- lookup_lines = False ,
497
- capture_locals = capture_locals ,
498
- _seen = _seen )
499
- else :
500
- cause = None
501
- if (exc_value and exc_value .__context__ is not None
502
- and id (exc_value .__context__ ) not in _seen ):
503
- context = TracebackException (
504
- type (exc_value .__context__ ),
505
- exc_value .__context__ ,
506
- exc_value .__context__ .__traceback__ ,
507
- limit = limit ,
508
- lookup_lines = False ,
509
- capture_locals = capture_locals ,
510
- _seen = _seen )
511
- else :
512
- context = None
513
- self .__cause__ = cause
514
- self .__context__ = context
515
- self .__suppress_context__ = \
516
- exc_value .__suppress_context__ if exc_value else False
517
488
# TODO: locals.
518
489
self .stack = StackSummary .extract (
519
490
walk_tb (exc_traceback ), limit = limit , lookup_lines = lookup_lines ,
@@ -532,6 +503,45 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
532
503
self .msg = exc_value .msg
533
504
if lookup_lines :
534
505
self ._load_lines ()
506
+ self .__suppress_context__ = \
507
+ exc_value .__suppress_context__ if exc_value else False
508
+
509
+ # Convert __cause__ and __context__ to `TracebackExceptions`s, use a
510
+ # queue to avoid recursion (only the top-level call gets _seen == None)
511
+ if not is_recursive_call :
512
+ queue = [(self , exc_value )]
513
+ while queue :
514
+ te , e = queue .pop ()
515
+ if (e and e .__cause__ is not None
516
+ and id (e .__cause__ ) not in _seen ):
517
+ cause = TracebackException (
518
+ type (e .__cause__ ),
519
+ e .__cause__ ,
520
+ e .__cause__ .__traceback__ ,
521
+ limit = limit ,
522
+ lookup_lines = lookup_lines ,
523
+ capture_locals = capture_locals ,
524
+ _seen = _seen )
525
+ else :
526
+ cause = None
527
+ if (e and e .__context__ is not None
528
+ and id (e .__context__ ) not in _seen ):
529
+ context = TracebackException (
530
+ type (e .__context__ ),
531
+ e .__context__ ,
532
+ e .__context__ .__traceback__ ,
533
+ limit = limit ,
534
+ lookup_lines = lookup_lines ,
535
+ capture_locals = capture_locals ,
536
+ _seen = _seen )
537
+ else :
538
+ context = None
539
+ te .__cause__ = cause
540
+ te .__context__ = context
541
+ if cause :
542
+ queue .append ((te .__cause__ , e .__cause__ ))
543
+ if context :
544
+ queue .append ((te .__context__ , e .__context__ ))
535
545
536
546
@classmethod
537
547
def from_exception (cls , exc , * args , ** kwargs ):
@@ -542,10 +552,6 @@ def _load_lines(self):
542
552
"""Private API. force all lines in the stack to be loaded."""
543
553
for frame in self .stack :
544
554
frame .line
545
- if self .__context__ :
546
- self .__context__ ._load_lines ()
547
- if self .__cause__ :
548
- self .__cause__ ._load_lines ()
549
555
550
556
def __eq__ (self , other ):
551
557
if isinstance (other , TracebackException ):
@@ -622,15 +628,32 @@ def format(self, *, chain=True):
622
628
The message indicating which exception occurred is always the last
623
629
string in the output.
624
630
"""
625
- if chain :
626
- if self .__cause__ is not None :
627
- yield from self .__cause__ .format (chain = chain )
628
- yield _cause_message
629
- elif (self .__context__ is not None and
630
- not self .__suppress_context__ ):
631
- yield from self .__context__ .format (chain = chain )
632
- yield _context_message
633
- if self .stack :
634
- yield 'Traceback (most recent call last):\n '
635
- yield from self .stack .format ()
636
- yield from self .format_exception_only ()
631
+
632
+ output = []
633
+ exc = self
634
+ while exc :
635
+ if chain :
636
+ if exc .__cause__ is not None :
637
+ chained_msg = _cause_message
638
+ chained_exc = exc .__cause__
639
+ elif (exc .__context__ is not None and
640
+ not exc .__suppress_context__ ):
641
+ chained_msg = _context_message
642
+ chained_exc = exc .__context__
643
+ else :
644
+ chained_msg = None
645
+ chained_exc = None
646
+
647
+ output .append ((chained_msg , exc ))
648
+ exc = chained_exc
649
+ else :
650
+ output .append ((None , exc ))
651
+ exc = None
652
+
653
+ for msg , exc in reversed (output ):
654
+ if msg is not None :
655
+ yield msg
656
+ if exc .stack :
657
+ yield 'Traceback (most recent call last):\n '
658
+ yield from exc .stack .format ()
659
+ yield from exc .format_exception_only ()
0 commit comments