[Python-ideas] Shrink recursion error tracebacks (was: Have REPL print less by default)

Steven D'Aprano steve at pearwood.info
Sat Apr 23 06:13:36 EDT 2016


On Sat, Apr 23, 2016 at 02:14:10AM -0400, Franklin? Lee wrote:

> Here's pseudocode for my suggestion.
> (I assume appropriate definitions of `traceback`, `print_folded`, and
> `print_the_thing`. I assume `func_name` is a qualified name (e.g.
> `('f', '<stdin>')`).)
> 
>     seen = collections.Counter() # Counts number of times each
> (func,line_no) has been seen
>     block = None # A block of potentially-hidden functions.
>     prev_line_no = None # In case len(block) == 1.
>     hidecount = 0 # Total number of hidden lines.
>     for func_name, line_no in traceback:
>         times = seen[func_name, line_no] += 1
>         if times >= 3:
>             if block is None:
>                 block = collections.Counter()
>             block[func_name] += 1
>             prev_line_no = line_no
>         else:
>             # This `if` can be a function which returns a hidecount,
>             #  so we don't repeat ourselves at the end of the loop.
>             if block is not None:
>                 if len(block) == 1: # don't need to hide
>                     print_the_thing(next(block.
> keys()), prev_line_no)
>                 else:
>                     print_folded(block)
>                     hidecount += len(block)
>                 block = None
>             print_the_thing(func_name, line_no)
> 
>     if block is not None:
>         if len(block) == 1:
>             print_the_thing(block[0])
>         else:
>             print_folded(block)
>             hidecount += len(block)



Just in case anyone missed it, here's my actual, working, code, which I 
now have in my PYTHONSTARTUP file.


import sys
import traceback
from itertools import groupby
TEMPLATE = "  [...previous call is repeated %d times...]\n"

def collapse(seq):
    for key, group in groupby(seq):
        group = list(group)
        if len(group) < 3:
            for item in group:
                yield item
        else:
            yield key
            yield TEMPLATE % (len(group)-1)

def shortertb(*args):
    lines = traceback.format_exception(*args)
    sys.stderr.write(''.join(collapse(lines)))

sys.excepthook = shortertb


And here is an actual working example of it in action:

py> import fact  # Uses the obvious recursive algorithm.
py> fact.fact(50000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/steve/python/fact.py", line 3, in fact
    return n*fact(n-1)
  [...previous call is repeated 997 times...]
  File "/home/steve/python/fact.py", line 2, in fact
    if n < 1: return 1
RuntimeError: maximum recursion depth exceeded in comparison


I'm not very interested in a complex, untested, incomplete, non-working 
chunk of pseudo-code when I have something which actually works in less 
than twenty lines. Especially since your version hides useful traceback 
information and requires the user to call a separate function to display 
the unmangled (and likely huge) traceback to find out what they're 
missing.

In my version, they're not missing anything: it's a simple run-length 
encoding of the tracebacks, and nothing is hidden. The output is just 
compressed.

So I'm afraid that, even if you manage to get your pseudo-code working 
and debugged, I'm going to vote a strong -1 on your proposal. Even if it 
works, I don't want lines to be hidden just because they've been seen 
before in some unrelated part of the traceback.

> My hiding is more complex (can't reproduce original output exactly),
> so it would be important to have an obvious way to get the old
> behavior. Someone else can propose the wording, if the hiding strategy
> itself seems useful.

To me, it seems harmful, not useful.


-- 
Steve


More information about the Python-ideas mailing list