Welcome, guest | Sign In | My Account | Store | Cart

Replaces the default exception hook with one that, upon "infinite recursion", removes the last cycle. This results in a significantly cleaner and shorter error message.

Usage: simply import <module> as _

For more details see the descussion here: https://mail.python.org/pipermail/python-ideas/2013-September/023190.html

Python, 79 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
'''
@since: 15/09/2013
@version: 0.3 06/07/2015

@author: Elazar Gershuni (elazarg at gmail)
'''

import sys
import traceback


def _find_last_cycle(tb):
    call_names = set()
    size = 0
    for i, call in enumerate(reversed(tb)):
        #call is not hashable in python3.5, but it has a name
        name = getattr(call, 'name', None) or hash(call)
        if size == 0:
            call_names.add(name)
            if call == tb[-1]:
                size = i
        elif name not in call_names:
            length = i
            break
    return size, length


def is_recursion_error(exctype, trace):
    if exctype.__name__ == 'RecursionError':
        return True
    if exctype is not RuntimeError:
        return False
    # prior to python3.5 there's no RecursionError,
    # so we check the size
    tb = traceback.extract_tb(trace)
    return len(tb) == sys.getrecursionlimit()


def cycle_detect_excepthook(exctype, value, trace):
    if is_recursion_error(exctype, trace):
        tb = traceback.extract_tb(trace)
        size, length = _find_last_cycle(tb)
        count = round(length / size, 2)
        if count >= 2:
            traceback.print_exception(exctype, value, trace, len(tb) - length + size)
            # sadly, we have no standard way to add this line to the real error stream
            sys.stderr.write('{} occurrences of cycle of size {} detected\n'
                            .format(count, size))
            return
    sys.__excepthook__(exctype, value, trace)

# the excepthook 
sys.excepthook = cycle_detect_excepthook

if __name__ == '__main__':
    def p2(): p0()
    
    def p1(): p2()
    
    def p0(): p1()
    
    def bar():
        p0()
        
    bar()


    '''
    Output in interactive mode:
    
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 2, in bar
      File "<stdin>", line 1, in p0
      File "<stdin>", line 1, in p1
      File "<stdin>", line 1, in p2
    RuntimeError: maximum recursion depth exceeded
    332.67 occurrences of cycle of size 3 detected    
    '''

I would like to hear about

  1. Better ways to detect/report cycles.
  2. A way to print the results to the correct output (which may not be sys.stderr).

A discussion in https://mail.python.org/mailman/listinfo/python-ideas