<div dir="rtl"><div dir="ltr">I suggest adding an excepthook that prints out a compressed version of the stack trace. The new excepthook should be the default at least for interactive mode.<br></div><div dir="ltr"><br></div>
<div dir="ltr">The use case is this: you are using an interactive interpreter, or perhaps in eclipse's PyDev, experimenting with some code. The code happen to has an infinite recursion - maybe an erroneous boundary condition, maybe the recursion itself was an accident. You didn't catch the RuntimeError so you get a print of the traceback. This is by default a 2000 lines of highly repetitive call chain. Most likely, a single cycle repeating some 300 times.</div>
<div dir="ltr"><br></div><div dir="ltr">The main problem is that in most environments, by default, you have only a limited amount of lines kept in the window. So you can't just scroll up and see what was the error in the first place - where is the entry point into the cycle. You have to reproduce it, and catch RuntimeError. You can't just use printing for debugging, either, because you won't see them. And even if you can see it, you had lost much of your "history" for nothing.</div>
<div dir="ltr"><br></div><div dir="ltr">I have tried to implement an alternative for sys.excepthook (see below), which compresses the last simple cycle in the call graph. Turns out it's not trivial, since the traceback object is not well documented (and maybe it shouldn't be, as it is an implementation detail) so it's non trivial (if at all possible) to change the trace list in an existing traceback. I don't think it is reasonable to just send anyone interested in such a feature to implement it themselves - especially given that newcomers ate its main target - and even if we do, there is no simple way to make it a default.</div>
<div dir="ltr"><br></div><div dir="ltr">Such a compression will not always help, since the call graph may be arbitrarily complex, so there has to be some threshold below which there won't be any compression. this threshold should be chosen after considering the number of lines accessible by default in common environments (Linux/Windows terminals, eclipse's console, etc.).<br>
Needless to say, the output should be correct in all cases. I am not sure that my example implementation is.</div><div dir="ltr"><br></div><div dir="ltr">Another suggestion, related but distinct: `class RecursionLimitError(RuntimeError)` should be raised instead of a plain RuntimeError. One should be able to except this specific case, and "Exception messages are not part of the Python API".</div>
<div dir="ltr"><br>---</div><div dir="ltr"><br>Example for the desired result (non interactive):<br><br><div dir="ltr">Traceback (most recent call last):</div><div dir="ltr">  File "/workspace/compress.py", line 48, in <module></div>
<div dir="ltr">    bar()</div><div dir="ltr">  File "/workspace/compress.py", line 46, in bar</div><div dir="ltr">    p()</div><div dir="ltr">  File "/workspace/compress.py", line 43, in p</div><div dir="ltr">
    def p(): p0()</div><div dir="ltr">  File "/workspace/compress.py", line 41, in p0</div><div dir="ltr">    def p0(): p2()</div><div dir="ltr">  File "/workspace/compress.py", line 39, in p2</div><div dir="ltr">
    def p2(): p()</div><div dir="ltr">RuntimeError: maximum recursion depth exceeded</div><div dir="ltr">332.67 occurrences of cycle of size 3 detected</div><div dir="ltr"><br></div><div>Code:</div><br><div dir="ltr"><div dir="ltr">
import traceback</div><div dir="ltr">import sys</div><div dir="ltr"><br></div><div dir="ltr">def print_exception(name, value, count, size, newtrace):</div><div>    # this is ugly and fragile</div><div dir="ltr">    sys.stderr.write('Traceback (most recent call last):\n')</div>
<div dir="ltr">    sys.stderr.writelines(traceback.format_list(newtrace))</div><div dir="ltr">    sys.stderr.write('{}: {}\n'.format(name ,value))</div><div dir="ltr">    sys.stderr.write('{} occurrences of cycle of size {} detected\n'.format(count, size))</div>
<div dir="ltr"><br></div><div dir="ltr">def analyze_cycles(tb):</div><div dir="ltr">    calls = set()</div><div dir="ltr">    size = 0</div><div dir="ltr">    for i, call in enumerate(reversed(tb)):</div><div dir="ltr">        if size == 0:</div>
<div dir="ltr">            calls.add(call)</div><div dir="ltr">            if call == tb[-1]:</div><div dir="ltr">                size = i</div><div dir="ltr">        elif call not in calls:</div><div dir="ltr">            length = i</div>
<div dir="ltr">            break</div><div dir="ltr">    return size, length</div><div dir="ltr"><br></div><div dir="ltr">def cycle_detect_excepthook(exctype, value, trace):</div><div dir="ltr">    if exctype is RuntimeError:</div>
<div dir="ltr">        tb = traceback.extract_tb(trace)</div><div>        # Feels like a hack here</div><div dir="ltr">        if len(tb) >= sys.getrecursionlimit()-1:</div><div dir="ltr">            size, length = analyze_cycles(tb)</div>
<div dir="ltr">            count = round(length/size, 2)</div><div dir="ltr">            if count >= 2:</div><div dir="ltr">                print_exception(exctype.__name__, value, count, size, tb[:-length+size])</div>
<div dir="ltr">                return</div><div dir="ltr">    sys.__excepthook__(exctype, value, tb)</div><div dir="ltr"><br></div><div dir="ltr">sys.excepthook = cycle_detect_excepthook</div><div dir="ltr"><br></div><div dir="ltr">
if __name__ == '__main__':</div><div dir="ltr">    def p2(): p()</div><div dir="ltr">    </div><div dir="ltr">    def p0(): p2()</div><div dir="ltr">    </div><div dir="ltr">    def p(): p()</div><div dir="ltr">    </div>
<div dir="ltr">    def bar():</div><div dir="ltr">        p()</div><div dir="ltr">        </div><div dir="ltr">    bar()</div><div><br></div><div>###</div><div><br></div><div>Elazar</div></div></div></div>