summaryrefslogtreecommitdiff
path: root/Lib/traceback.py
diff options
context:
space:
mode:
authorRobert Collins <rbtcollins@hp.com>2015-03-05 20:28:52 +1300
committerRobert Collins <rbtcollins@hp.com>2015-03-05 20:28:52 +1300
commitd7c7e0ef69e0aacc24d34388dd68927f7f7ee1f3 (patch)
treeebb63d731b8e9d84d4285b59b4e970cf509e9eea /Lib/traceback.py
parent2856332f5efa28c2abf9805cfdfdbf10b733b231 (diff)
downloadcpython-git-d7c7e0ef69e0aacc24d34388dd68927f7f7ee1f3.tar.gz
Issue #22936: Make it possible to show local variables in tracebacks.
Diffstat (limited to 'Lib/traceback.py')
-rw-r--r--Lib/traceback.py48
1 files changed, 32 insertions, 16 deletions
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 72e1e2a9f9..0ac181923d 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.