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

Steven D'Aprano steve at pearwood.info
Wed Apr 20 22:46:20 EDT 2016

On Wed, Apr 20, 2016 at 12:07:11AM -0400, Émanuel Barry wrote:
> This idea was mentioned a couple of times in the previous thread, and it
> seems reasonable to me. Getting recursion errors when testing a function in
> the interactive prompt often screwed me over, as I have a limited scrollback
> of 1000 lines at best on Windows (I never checked exactly how high/low the
> limit was), and with Python's recursion limit of 1000, that's a whopping
> 1000 to 2000 lines in my console, effectively killing off anything useful I
> might have wanted to see. Such as, for example, the function definition that
> triggered the exception.

Even if you have an effectively unlimited scrollback, getting thousands 
of identical lines is a PITA and rather distracting and annoying.

> I'm suggesting a change to how Python handles specifically recursion limits,

We should not limit this to specifically *recursion* limits, despite the 
error message printed out, it is not just recursive calls that count 
towards the recursion limit. It is any function call.

In practice, though, it is difficult to run into this limit with regular 
non-recursive calls, but not impossible.

> to cut after a sane number of times (someone suggested to shrink the output
> after 3 times), and simply stating how many more identical messages there
> are. 

That would be me :-)

> I would also like to extend this to any arbitrary loop of callers (for
> example foo -> bar -> baz -> foo and so on would be counted as one "call"
> for the purposes of this proposal).

Seems reasonable.

> We can probably afford to cut out immediately as we notice the recursion,

No you can't. A bunch of recursive calls may be followed by non- 
recursive calls, or a different set of recursive calls. It might not 
even be a RuntimeError at the end.

For example, put these four functions in a file, "fact.py":

def fact(n):
    if n < 1: return 1
    return n*fact(n-1)

def rec(n):
    if n < 20:
        return spam(n)
    return n + rec(n-1)

def spam(n):
    return eggs(2*n)

def eggs(arg):
    return fact(arg//2)

Now I run this:

import fact

and I get this shorter traceback:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/steve/python/fact.py", line 9, in rec
    return n + rec(n-1)
  [...repeat previous line 10 times...]
  File "/home/steve/python/fact.py", line 8, in rec
    return spam(n)
  File "/home/steve/python/fact.py", line 12, in spam
    return eggs(2*n)
  File "/home/steve/python/fact.py", line 15, in eggs
    return fact(arg//2)
  File "/home/steve/python/fact.py", line 3, in fact
    return n*fact(n-1)
  [...repeat previous line 18 times...]
  File "/home/steve/python/fact.py", line 2, in fact
    if n < 1: return 1
RuntimeError: maximum recursion depth exceeded in comparison

The point being, you cannot just stop processing traces when you find 
one repeated.


More information about the Python-ideas mailing list