Может ли метод Python проверить, был ли он вызван из самого себя? - PullRequest
9 голосов
/ 26 октября 2011

Допустим, у меня есть функция Python f и fhelp. fhelp предназначен для рекурсивного вызова себя. f не следует вызывать рекурсивно. Есть ли способ для f определить, был ли он вызван рекурсивно?

Ответы [ 2 ]

14 голосов
/ 26 октября 2011

Используйте для этого модуль 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, вызов был (in)прямо рекурсивный.Метод 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
1 голос
/ 26 октября 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, и это вызовет исключение.

...