Welcome, guest | Sign In | My Account | Store | Cart
'''
@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    
    '''

Diff to Previous Revision

--- revision 2 2015-07-05 23:35:17
+++ revision 3 2015-07-05 23:46:59
@@ -13,26 +13,27 @@
     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(call.name)
+            call_names.add(name)
             if call == tb[-1]:
                 size = i
-        elif call.name not in call_names:
+        elif name not in call_names:
             length = i
             break
     return size, length
 
 
 def is_recursion_error(exctype, trace):
-    try: 
-        return exctype is RecursionError
-    except NameError:
-        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() - 1
+    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):

History