Reputation: 16580
Let's say I have a Python function f
and fhelp
. fhelp
is designed to call itself recursively. f
should not be called recursively. Is there a way for f
to determine if it has been called recursively?
Upvotes: 11
Views: 6575
Reputation: 4319
I have improved jro's answer to make it work with objects while making it explicitly threadsafe:
import threading
class NoRecurse:
def __init__(self):
self.lock = threading.RLock()
self.seen = set()
def __call__(no_recurse, f):
def func(self, *args, **kwargs):
with no_recurse.lock:
if len([l[2] for l in traceback.extract_stack() if l[2] == f.__name__]) > 0 and self in no_recurse.seen:
raise Exception('Recursed')
with no_recurse.lock:
no_recurse.seen.add(self)
r = f(self, *args, **kwargs)
with no_recurse.lock:
no_recurse.seen.remove(self)
return r
return func
Upvotes: 0
Reputation: 9484
Use the traceback module for this:
>>> import traceback
>>> def f(depth=0):
... print depth, traceback.print_stack()
... if depth < 2:
... f(depth + 1)
...
>>> f()
0 File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f
None
1 File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in f
File "<stdin>", line 2, in f
None
2 File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in f
File "<stdin>", line 4, in f
File "<stdin>", line 2, in f
None
So, if any entry in the stack indicates that the code was called from f
, the call was (in)directly recursive. The traceback.extract_stack
method gives you an easy access to this data. The if len(l[2] ...
statement in the example below simply counts the number of exact matches of the name of the function. To make it even prettier (thanks to agf for the idea), you could make it into a decorator:
>>> def norecurse(f):
... def func(*args, **kwargs):
... if len([l[2] for l in traceback.extract_stack() if l[2] == f.__name__]) > 0:
... raise Exception('Recursed')
... return f(*args, **kwargs)
... return func
...
>>> @norecurse
... def foo(depth=0):
... print depth
... foo(depth + 1)
...
>>> foo()
0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in func
File "<stdin>", line 4, in foo
File "<stdin>", line 5, in func
Exception: Recursed
Upvotes: 16
Reputation: 176960
You could use a flag set by a decorator:
def norecurse(func):
func.called = False
def f(*args, **kwargs):
if func.called:
print "Recursion!"
# func.called = False # if you are going to continue execution
raise Exception
func.called = True
result = func(*args, **kwargs)
func.called = False
return result
return f
Then you can do
@norecurse
def f(some, arg, s):
do_stuff()
and if f
gets called again while it's running, called
will be True
and it will raise an exeption.
Upvotes: 3