Допустим, у меня есть функция Python f
и fhelp
. fhelp
предназначен для рекурсивного вызова самого себя. f
не следует вызывать рекурсивно. Есть ли способ для f
определить, был ли он вызван рекурсивно?
Может ли метод Python проверить, был ли он вызван изнутри самого себя?
Ответы (2)
Для этого используйте модуль traceback:
>>> 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
Таким образом, если какая-либо запись в стеке указывает на то, что код был вызван из f
, вызов был (не)прямо рекурсивным. Метод traceback.extract_stack
дает вам легкий доступ к этим данным. Оператор if len(l[2] ...
в приведенном ниже примере просто подсчитывает количество точных совпадений имени функции. Чтобы сделать его еще красивее (спасибо agf за идею), вы можете превратить его в декоратор:
>>> def norecurse(f):
... def func(*args, **kwargs):
... if len([l[2] for l in traceback.extract_stack() if l[2] == f.func_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
person
jro
schedule
26.10.2011
Вы можете использовать флаг, установленный декоратором:
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
Тогда вы можете сделать
@norecurse
def f(some, arg, s):
do_stuff()
и если f
будет вызван снова во время его работы, called
будет True
и вызовет исключение.
person
agf
schedule
26.10.2011
У этого есть некоторая нежелательная память: скажем, ваш
f
определяется так: def f(func): print func()
. Если вы вызовете его с помощью f(f)
, вы получите предупреждение о рекурсии. Если вы затем вызовете его с помощью f(lambda: 'foo')
, вы также получите предупреждение, хотя оно не рекурсивно...
- person jro; 26.10.2011
Это не будет надежно работать в приложении, где функция вызывается из нескольких потоков, хотя я предполагаю, что она может быть адаптирована для работы и там с помощью threading.local в качестве хранилища для логического значения.
- person Lauritz V. Thaulow; 26.10.2011
@jro Я предположил, что это для целей отладки, и OP не хочет продолжать выполнение, если обнаружена нежелательная рекурсия. Если он действительно хочет продолжить, достаточно просто добавить
func.called = False
перед raise
ing.
- person agf; 26.10.2011
@lazyr Почему я мог предположить, что код должен быть потокобезопасным, если это не упоминалось в вопросе?
- person agf; 26.10.2011
Я не имел в виду, что вы должны были. Комментарий должен был предостеречь и дать совет тем, кто может использовать это решение в таком случае. Решение
traceback
является потокобезопасным, а это — нет, но его можно сделать таким.
- person Lauritz V. Thaulow; 26.10.2011
Вероятно, вам следует использовать
try: result = func(*args, **kwargs) finally: func.called = False
на тот случай, если обернутая функция выдает исключение.
- person Duncan; 26.10.2011
f
изf
? - person madjar   schedule 26.10.2011f
не следует вызывать рекурсивно. Кстати. принимает лиf
какие-либо пользовательские функции в качестве входных данных? Если нет, вы, как автор этой функции, должны быть в состоянии убедиться, что она не вызывает себя рекурсивно. - person Björn Pollex   schedule 26.10.2011f
вызывает предоставленную пользователем функциюk
, которая, в свою очередь, снова вызываетf
. - person Björn Pollex   schedule 26.10.2011