@@ -349,10 +349,21 @@ def isexceptiontype(exc):
349349 if finallyf is not None :
350350 finallyf ()
351351
352- def equip_with_traceback (exc , depth = 0 ): # Python 3.7+
352+ def equip_with_traceback (exc , stacklevel = 1 ): # Python 3.7+
353353 """Given an exception instance exc, equip it with a traceback.
354354
355- `depth` is the starting depth for `sys._getframe`.
355+ `stacklevel` is the starting depth below the top of the call stack,
356+ to cull useless detail:
357+ - `0` means the trace includes everything, also
358+ `equip_with_traceback` itself,
359+ - `1` means the trace includes everything up to the caller,
360+ - And so on.
361+
362+ So typically, for direct use of this function `stacklevel` should
363+ be `1` (so it excludes `equip_with_traceback` itself, but shows
364+ all stack levels from your code), and for use in a utility function
365+ that itself is called from your code, it should be `2` (so it excludes
366+ the utility function, too).
356367
357368 The return value is `exc`, with its traceback set to the produced
358369 traceback.
@@ -361,13 +372,12 @@ def equip_with_traceback(exc, depth=0): # Python 3.7+
361372
362373 When not supported, raises `NotImplementedError`.
363374
364- This is useful in some special cases only, mainly when `raise` cannot be
365- used for some reason, and a manually created exception instance needs a
366- traceback. (E.g. in implementing the system for conditions and restarts.)
375+ This is useful mainly in special cases, where `raise` cannot be used for
376+ some reason, and a manually created exception instance needs a traceback.
377+ (The `signal` function in the conditions- and- restarts system uses this .)
367378
368- The `sys._getframe` function exists in CPython and in PyPy3,
369- but for another arbitrary Python implementation this is not
370- guaranteed.
379+ **CAUTION**: The `sys._getframe` function exists in CPython and in PyPy3,
380+ but for another arbitrary Python implementation this is not guaranteed.
371381
372382 Based on solution by StackOverflow user Zbyl:
373383 https://stackoverflow.com/a/54653137
@@ -379,23 +389,37 @@ def equip_with_traceback(exc, depth=0): # Python 3.7+
379389 """
380390 if not isinstance (exc , BaseException ):
381391 raise TypeError ("exc must be an exception instance; got {} with value '{}'" .format (type (exc ), exc ))
392+ if not isinstance (stacklevel , int ):
393+ raise TypeError ("stacklevel must be int, got {} with value '{}'" .format (type (stacklevel ), stacklevel ))
394+ if stacklevel < 0 :
395+ raise ValueError ("stacklevel must be >= 0, got {}" .format (stacklevel ))
382396
383397 try :
384398 getframe = sys ._getframe
385399 except AttributeError as err : # pragma: no cover, both CPython and PyPy3 have sys._getframe.
386400 raise NotImplementedError ("Need a Python interpreter which has `sys._getframe`" ) from err
387401
388- tb = None
402+ frames = []
403+ depth = stacklevel
389404 while True :
390- # Starting from given depth, get all frames up to the root of the call stack.
391405 try :
392- frame = getframe (depth )
406+ frames . append ( getframe (depth )) # 0 = top of call stack
393407 depth += 1
394- except ValueError :
408+ except ValueError : # beyond the root level
395409 break
396- # Python 3.7+ allows creating types.TracebackType objects from Python code.
410+
411+ # Python 3.7+ allows creating `types.TracebackType` objects in Python code.
397412 try :
398- tb = TracebackType (tb , frame , frame .f_lasti , frame .f_lineno )
413+ tracebacks = []
414+ nxt = None # tb_next should point toward the level where the exception occurred.
415+ for frame in frames : # walk from top of call stack toward the root
416+ tb = TracebackType (nxt , frame , frame .f_lasti , frame .f_lineno )
417+ tracebacks .append (tb )
418+ nxt = tb
419+ if tracebacks :
420+ tb = tracebacks [- 1 ] # root level
421+ else :
422+ tb = None
399423 except TypeError as err : # Python 3.6 or earlier
400424 raise NotImplementedError ("Need Python 3.7 or later to create traceback objects" ) from err
401425 return exc .with_traceback (tb ) # Python 3.7+
0 commit comments