Index: Demo/newfeatures/demo-bindann.py =================================================================== --- Demo/newfeatures/demo-bindann.py (revision 0) +++ Demo/newfeatures/demo-bindann.py (revision 0) @@ -0,0 +1,33 @@ +#! /usr/bin/env python2.6 +''' +demo-bindann -this requires the bindann patch to the stdlib. +''' + +import sys +import inspect +import traceback + + +def main(args=sys.argv[1:]): + print 'This demonstrates binding-annoted exception tracebacks.' + print 'Consider the following function:' + print inspect.getsource(f) + print '-With standard tracebacks, calling f(42) gives:' + try: + f(42) + except: + traceback.print_exc() + + print '\n-And now with annotated exceptions:' + # Install an annotating exception handler: + sys.excepthook = lambda t, v, b: traceback.print_exception(t, v, b, annotate=True) + f(42) + + +def f(c): + for char in c + " hello": + print 'The string contains "%s".' % (char,) + + +if __name__ == '__main__': + main() Property changes on: Demo/newfeatures/demo-bindann.py ___________________________________________________________________ Name: svn:executable + * Index: Demo/newfeatures/README =================================================================== --- Demo/newfeatures/README (revision 0) +++ Demo/newfeatures/README (revision 0) @@ -0,0 +1,3 @@ + +This directory contains Demos of new python language features. + Index: Lib/traceback.py =================================================================== --- Lib/traceback.py (revision 53755) +++ Lib/traceback.py (working copy) @@ -1,8 +1,16 @@ -"""Extract, format and print information about Python stack traces.""" +""" +Extract, format and print information about Python stack traces. +To display binding annotations for uncaught exceptions do this: + +import sys, traceback +sys.excepthook = lambda t, v, b: traceback.print_exception(t, v, b, annotate=True) +""" + import linecache import sys import types +from bindann import annotate_bindings __all__ = ['extract_stack', 'extract_tb', 'format_exception', 'format_exception_only', 'format_list', 'format_stack', @@ -43,13 +51,15 @@ return list -def print_tb(tb, limit=None, file=None): +def print_tb(tb, limit=None, file=None, annotate=False): """Print up to 'limit' stack trace entries from the traceback 'tb'. If 'limit' is omitted or None, all entries are printed. If 'file' is omitted or None, the output goes to sys.stderr; otherwise 'file' should be an open file or file-like object with a write() - method. + method. If annotate is True, add binding annotations + to available source lines. When False or ommitted, these annotations + are absent. """ if file is None: file = sys.stderr @@ -67,15 +77,17 @@ ' File "%s", line %d, in %s' % (filename,lineno,name)) linecache.checkcache(filename) line = linecache.getline(filename, lineno, f.f_globals) - if line: _print(file, ' ' + line.strip()) + if line: + line = possibly_annotate(line, f, annotate) + _print(file, ' ' + line.strip()) tb = tb.tb_next n = n+1 -def format_tb(tb, limit = None): +def format_tb(tb, limit = None, annotate=False): """A shorthand for 'format_list(extract_stack(f, limit)).""" - return format_list(extract_tb(tb, limit)) + return format_list(extract_tb(tb, limit, annotate)) -def extract_tb(tb, limit = None): +def extract_tb(tb, limit = None, annotate=False): """Return list of up to limit pre-processed entries from traceback. This is useful for alternate formatting of stack traces. If @@ -99,7 +111,7 @@ name = co.co_name linecache.checkcache(filename) line = linecache.getline(filename, lineno, f.f_globals) - if line: line = line.strip() + if line: line = possibly_annotate(line, f, annotate).strip() else: line = None list.append((filename, lineno, name, line)) tb = tb.tb_next @@ -107,7 +119,7 @@ return list -def print_exception(etype, value, tb, limit=None, file=None): +def print_exception(etype, value, tb, limit=None, file=None, annotate=False): """Print exception up to 'limit' stack trace entries from 'tb' to 'file'. This differs from print_tb() in the following ways: (1) if @@ -122,13 +134,13 @@ file = sys.stderr if tb: _print(file, 'Traceback (most recent call last):') - print_tb(tb, limit, file) + print_tb(tb, limit, file, annotate) lines = format_exception_only(etype, value) for line in lines[:-1]: _print(file, line, ' ') _print(file, lines[-1], '') -def format_exception(etype, value, tb, limit = None): +def format_exception(etype, value, tb, limit = None, annotate=False): """Format a stack trace and the exception information. The arguments have the same meaning as the corresponding arguments @@ -139,7 +151,7 @@ """ if tb: list = ['Traceback (most recent call last):\n'] - list = list + format_tb(tb, limit) + list = list + format_tb(tb, limit, annotate) else: list = [] list = list + format_exception_only(etype, value) @@ -216,7 +228,7 @@ return '' % type(value).__name__ -def print_exc(limit=None, file=None): +def print_exc(limit=None, file=None, annotate=False): """Shorthand for 'print_exception(sys.exc_type, sys.exc_value, sys.exc_traceback, limit, file)'. (In fact, it uses sys.exc_info() to retrieve the same information in a thread-safe way.)""" @@ -224,30 +236,30 @@ file = sys.stderr try: etype, value, tb = sys.exc_info() - print_exception(etype, value, tb, limit, file) + print_exception(etype, value, tb, limit, file, annotate) finally: etype = value = tb = None -def format_exc(limit=None): +def format_exc(limit=None, annotate=False): """Like print_exc() but return a string.""" try: etype, value, tb = sys.exc_info() - return ''.join(format_exception(etype, value, tb, limit)) + return ''.join(format_exception(etype, value, tb, limit, annotate)) finally: etype = value = tb = None -def print_last(limit=None, file=None): +def print_last(limit=None, file=None, annotate=False): """This is a shorthand for 'print_exception(sys.last_type, sys.last_value, sys.last_traceback, limit, file)'.""" if file is None: file = sys.stderr print_exception(sys.last_type, sys.last_value, sys.last_traceback, - limit, file) + limit, file, annotate) -def print_stack(f=None, limit=None, file=None): +def print_stack(f=None, limit=None, file=None, annotate=False): """Print a stack trace from its invocation point. The optional 'f' argument can be used to specify an alternate @@ -259,18 +271,18 @@ raise ZeroDivisionError except ZeroDivisionError: f = sys.exc_info()[2].tb_frame.f_back - print_list(extract_stack(f, limit), file) + print_list(extract_stack(f, limit, annotate), file) -def format_stack(f=None, limit=None): +def format_stack(f=None, limit=None, annotate=False): """Shorthand for 'format_list(extract_stack(f, limit))'.""" if f is None: try: raise ZeroDivisionError except ZeroDivisionError: f = sys.exc_info()[2].tb_frame.f_back - return format_list(extract_stack(f, limit)) + return format_list(extract_stack(f, limit, annotate=False)) -def extract_stack(f=None, limit = None): +def extract_stack(f=None, limit = None, annotate=False): """Extract the raw traceback from the current stack frame. The return value has the same format as for extract_tb(). The @@ -296,7 +308,7 @@ name = co.co_name linecache.checkcache(filename) line = linecache.getline(filename, lineno, f.f_globals) - if line: line = line.strip() + if line: line = possibly_annotate(line, f, annotate).strip() else: line = None list.append((filename, lineno, name, line)) f = f.f_back @@ -310,3 +322,13 @@ Obsolete in 2.3. """ return tb.tb_lineno + +def possibly_annotate(line, frame, annotate): + ''' + Return line untouched, unless annotate is true, in which case the + binding annotated line is returned. + ''' + if annotate: + return annotate_bindings(line, frame, sep='\n ') + else: + return line Index: Lib/bindann.py =================================================================== --- Lib/bindann.py (revision 0) +++ Lib/bindann.py (revision 0) @@ -0,0 +1,86 @@ +''' +bindann - BINDing ANNotation. +''' + +import tokenize, token + +def annotate_bindings(line, frame, sep='\n'): + ''' + Given a line of semi-complete source code and a frame object, return + a string giving bindings relevant to that line of source. + ''' + chunks = [] + bindings = get_bindings(line, frame) + if bindings: + chunks.append('# With bindings:') + for name, value in sorted(bindings.items()): + repstr = repr(bindings[name]) + if repstr.startswith('<'): + chunks.append('# %s = %s' % (name, repstr)) + else: + chunks.append('%s = %s' % (name, repstr)) + chunks.extend(['# Source:', line.strip()]) + return sep.join(chunks) + + +def get_bindings(line, frame): + ''' + Given line and frame as for annotate_bindings, return a mapping of relevant bindings. + ''' + bindings = {} + scopes = Scopes(frame.f_locals, frame.f_globals, frame.f_builtins) + allnames = frame.f_code.co_names + frame.f_code.co_freevars + frame.f_code.co_varnames + varnames = match_references(line, allnames) + if varnames: + for varname in varnames: + try: + bindings[varname] = scopes[varname] + except KeyError, e: + # Sometimes a line may reference a non-existing binding, + # such as in an unfinished assignment: + # x = 3 + 'foo' # Exception without binding x... + bindings[varname] = UnboundLocal() + pass # Oh well. + return bindings + +def match_references(expr, names): + refs = set(find_all_names(expr)) + refs = refs.intersection(names) + return sorted(refs) + + +def find_all_names(text): + # yikes! rl emulates a readline interface to a single string: + def rl(chunks = [text, '']): + return chunks.pop(0) + + try: + for tokinfo in tokenize.generate_tokens(rl): + tt, ts = tokinfo[:2] + if tt == token.NAME: + yield ts + except tokenize.TokenError, e: + # We don't mind early EOFs: + if e.args[0] != 'EOF in multi-line statement': + raise + + +class Scopes (object): + def __init__(self, *scopes): + self.scopes = scopes + + def __getitem__(self, key): + for scope in self.scopes: + try: + return scope[key] + except KeyError: + pass + raise KeyError('%r not in %r' % (key, [s.keys() for s in self.scopes])) + + +class UnboundLocal: + '''This class is used to indicate unbound references.''' + def __repr__(self): + return '<%s>' % self.__class__.__name__ + +