décorateur, recursion, explosion de la pile
Contexte:
Il arrive parfois d'avoir l'erreur: RecursionError: maximum recursion depth exceeded.
Il s'agit généralement d'un bug mais pas toujours, parfois il faudrait pousser cette limite plus loin quand on n'arrive pas à transformer notre code en code itératif.
Plus particulièrement, si le nombre de couches de récursion dépend du volume de donnée traité, il faut pouvoir changer cette limite dynamiquement.
C'est pourquoi j'ai écrit le décorateur suivant:
Code:
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
|
import copy
import sys
import logging
def _improve_recursion_decorateur(increment):
"""
permet d'eviter les problemes de limites de recursion
car si il y a un probleme, la limite est augmentee de 'increment'
"""
assert type(increment) is int, "'increment' doit etre un entier, '%s' n'est pas un entier, c'est un %s." % (increment, type(increment))
assert increment > 0, "'increment' doit etre strictement positif, ce n'est pas le cas de %d." % increment
def decorateur(fonction_a_executer):
def fonction_modifiee(*args, **kwargs):
args_copie = copy.copy(args)
kwargs_copie = copy.copy(kwargs)
while 1:
try:
return fonction_a_executer(*args_copie, **kwargs_copie)
except RecursionError as e:
if sys.getrecursionlimit() >= 10000: # si on a deja depasse une limite raisonable de recursion
raise e from e # on est surement dans une boucle infinie, donc on ne s'entete pas
try:
avant = sys.getrecursionlimit()
sys.setrecursionlimit(min(10000, avant+increment)) # si il y a trop de recursions par exemple a + b + c - g * 3 + ...
except Exception as e:
raise e from e
logging.warning("Recursion limit increased from %d to %d." % (avant, sys.getrecursionlimit()))
continue
except Exception as e:
raise e from e
return fonction_modifiee
return decorateur |
Il fonctionne environ, je dit environ car il dépasse de beaucoup le nombre de récursion que l'on imagine.
Un petit exemple:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
>>> @ _improve_recursion_decorateur(20)
... def f(n):
... return f(n-1) if n else "fini"
...
>>> f(2000)
WARNING:root:Recursion limit increased from 1000 to 1020.
WARNING:root:Recursion limit increased from 1020 to 1040.
WARNING:root:Recursion limit increased from 1040 to 1060.
WARNING:root:Recursion limit increased from 1060 to 1080.
...
WARNING:root:Recursion limit increased from 5960 to 5980.
WARNING:root:Recursion limit increased from 5980 to 6000.
WARNING:root:Recursion limit increased from 6000 to 6020.
'fini'
>>> |
Pourquoi il va jusqu’à 6000 alors qu'il n'y a qu'environ 2000 "boucles"! :traine: