Here's a decorator that will prevent a recursive function from calling itself:
def norecursion(default=None): '''Prevents recursion into the wrapped function.''' def entangle(f): def inner(*args, **kwds): if not hasattr(f, 'callcount'): f.callcount = 0 if f.callcount >= 1: print "recursion detected %s calls deep. exiting." % f.callcount return default else: f.callcount += 1 x = f(*args, **kwds) f.callcount -= 1 return x return inner return entangle
It's based on this recipe. The function in that recipe relies on keeping track of which arguments were passed into the function, which means that it could not work on a function without any arguments. The decorator above works by attaching an attribute to the wrapped function for keeping track of how many calls have been made and exiting when the number of nested calls goes above a certain number.
Here's how you use it:
@norecursion(default=1) def fact(x): if x <= 1: return 1 else: return x*fact(x-1)
Now when you call fact it won't make the recursive call, instead it will return the default value of 1:
>>> fact(0) 1 >>> fact(1) 1 >>> fact(2) recursion detected 1 calls deep. exiting. 2 >>> fact(3) recursion detected 1 calls deep. exiting. 3
Why I needed this: I have a function on a Jinja2 template which builds a list of all pages and their metadata (a bunch of variables defined at the top of the template). Let's say I use the function on index.html. When it iterates over all the pages, it comes to index.html and then tries to get the list of all pages again. This causes the infinite recursion. On the second call deep, I don't need the whole page list, I only need the template metadata, so I can safely wrap the function in @norecursion(default=) to prevent it from running subsequent times.
Update: Reading this post again I think I could have just used a memoization decorator instead. At the time preventing recursion with a decorator seemed like an okay solution, but memoization would have been a little less weird and probably worked fine.blog comments powered by Disqus