Issue #22936: Make it possible to show local variables in tracebacks.
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 72e1e2a..0ac1819 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -223,19 +223,19 @@
     - :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 @@
         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 @@
     """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 @@
             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 @@
             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 @@
         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 @@
     - :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 @@
                 exc_value.__cause__.__traceback__,
                 limit=limit,
                 lookup_lines=False,
+                capture_locals=capture_locals,
                 _seen=_seen)
         else:
             cause = None
@@ -422,6 +436,7 @@
                 exc_value.__context__.__traceback__,
                 limit=limit,
                 lookup_lines=False,
+                capture_locals=capture_locals,
                 _seen=_seen)
         else:
             context = None
@@ -431,7 +446,8 @@
             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 @@
         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.