diff options
author | 2015-03-05 20:28:52 +1300 | |
---|---|---|
committer | 2015-03-05 20:28:52 +1300 | |
commit | d7c7e0ef69e0aacc24d34388dd68927f7f7ee1f3 (patch) | |
tree | ebb63d731b8e9d84d4285b59b4e970cf509e9eea /Lib/traceback.py | |
parent | Add scripts for uploading Windows builds to a Linux server using pscp (diff) | |
download | cpython-d7c7e0ef69e0aacc24d34388dd68927f7f7ee1f3.tar.gz cpython-d7c7e0ef69e0aacc24d34388dd68927f7f7ee1f3.tar.bz2 cpython-d7c7e0ef69e0aacc24d34388dd68927f7f7ee1f3.zip |
Issue #22936: Make it possible to show local variables in tracebacks.
Diffstat (limited to 'Lib/traceback.py')
-rw-r--r-- | Lib/traceback.py | 48 |
1 files changed, 32 insertions, 16 deletions
diff --git a/Lib/traceback.py b/Lib/traceback.py index 72e1e2a9f93..0ac181923d7 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -223,19 +223,19 @@ class FrameSummary: - :attr:`line` The text from the linecache module for the of code that was running when the frame was captured. - :attr:`locals` Either None if locals were not supplied, or a dict - mapping the name to the str() of the variable. + mapping the name to the repr() of the variable. """ __slots__ = ('filename', 'lineno', 'name', '_line', 'locals') - def __init__(self, filename, lineno, name, lookup_line=True, locals=None, - line=None): + def __init__(self, filename, lineno, name, *, lookup_line=True, + locals=None, line=None): """Construct a FrameSummary. :param lookup_line: If True, `linecache` is consulted for the source code line. Otherwise, the line will be looked up when first needed. :param locals: If supplied the frame locals, which will be captured as - strings. + object representations. :param line: If provided, use this instead of looking up the line in the linecache. """ @@ -246,7 +246,7 @@ class FrameSummary: if lookup_line: self.line self.locals = \ - dict((k, str(v)) for k, v in locals.items()) if locals else None + dict((k, repr(v)) for k, v in locals.items()) if locals else None def __eq__(self, other): return (self.filename == other.filename and @@ -299,7 +299,8 @@ class StackSummary(list): """A stack of frames.""" @classmethod - def extract(klass, frame_gen, limit=None, lookup_lines=True): + def extract(klass, frame_gen, *, limit=None, lookup_lines=True, + capture_locals=False): """Create a StackSummary from a traceback or stack object. :param frame_gen: A generator that yields (frame, lineno) tuples to @@ -308,6 +309,8 @@ class StackSummary(list): include. :param lookup_lines: If True, lookup lines for each frame immediately, otherwise lookup is deferred until the frame is rendered. + :param capture_locals: If True, the local variables from each frame will + be captured as object representations into the FrameSummary. """ if limit is None: limit = getattr(sys, 'tracebacklimit', None) @@ -324,7 +327,12 @@ class StackSummary(list): fnames.add(filename) linecache.lazycache(filename, f.f_globals) # Must defer line lookups until we have called checkcache. - result.append(FrameSummary(filename, lineno, name, lookup_line=False)) + if capture_locals: + f_locals = f.f_locals + else: + f_locals = None + result.append(FrameSummary( + filename, lineno, name, lookup_line=False, locals=f_locals)) for filename in fnames: linecache.checkcache(filename) # If immediate lookup was desired, trigger lookups now. @@ -356,11 +364,16 @@ class StackSummary(list): newlines as well, for those items with source text lines. """ result = [] - for filename, lineno, name, line in self: - item = ' File "{}", line {}, in {}\n'.format(filename, lineno, name) - if line: - item = item + ' {}\n'.format(line.strip()) - result.append(item) + for frame in self: + row = [] + row.append(' File "{}", line {}, in {}\n'.format( + frame.filename, frame.lineno, frame.name)) + if frame.line: + row.append(' {}\n'.format(frame.line.strip())) + if frame.locals: + for name, value in sorted(frame.locals.items()): + row.append(' {name} = {value}\n'.format(name=name, value=value)) + result.append(''.join(row)) return result @@ -392,8 +405,8 @@ class TracebackException: - :attr:`msg` For syntax errors - the compiler error message. """ - def __init__(self, exc_type, exc_value, exc_traceback, limit=None, - lookup_lines=True, _seen=None): + def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, + lookup_lines=True, capture_locals=False, _seen=None): # NB: we need to accept exc_traceback, exc_value, exc_traceback to # permit backwards compat with the existing API, otherwise we # need stub thunk objects just to glue it together. @@ -411,6 +424,7 @@ class TracebackException: exc_value.__cause__.__traceback__, limit=limit, lookup_lines=False, + capture_locals=capture_locals, _seen=_seen) else: cause = None @@ -422,6 +436,7 @@ class TracebackException: exc_value.__context__.__traceback__, limit=limit, lookup_lines=False, + capture_locals=capture_locals, _seen=_seen) else: context = None @@ -431,7 +446,8 @@ class TracebackException: exc_value.__suppress_context__ if exc_value else False # TODO: locals. self.stack = StackSummary.extract( - walk_tb(exc_traceback), limit=limit, lookup_lines=lookup_lines) + walk_tb(exc_traceback), limit=limit, lookup_lines=lookup_lines, + capture_locals=capture_locals) self.exc_type = exc_type # Capture now to permit freeing resources: only complication is in the # unofficial API _format_final_exc_line @@ -512,7 +528,7 @@ class TracebackException: msg = self.msg or "<no detail available>" yield "{}: {}\n".format(stype, msg) - def format(self, chain=True): + def format(self, *, chain=True): """Format the exception. If chain is not *True*, *__cause__* and *__context__* will not be formatted. |