【问题标题】:Can a Python method check if it has been called from within itself?Python 方法是否可以检查它是否已从自身内部调用?
【发布时间】:2011-12-15 13:36:07
【问题描述】:

假设我有一个 Python 函数 ffhelpfhelp 旨在递归调用自身。 f 不应该被递归调用。有没有办法让f 确定它是否被递归调用?

【问题讨论】:

  • 好吧,你不能不从f 打电话给f 吗?
  • Python 的理念是我们都是明智的成年人,因此,请阅读并尊重文档。只需在您的文档中添加注释,说明不应递归调用 f。顺便提一句。 f 是否将任何用户定义的函数作为输入?如果没有,您作为该函数的作者应该能够确保它不会递归调用自身。
  • @madjar:递归调用可以是间接的:f 调用用户提供的函数k,而后者又再次调用f
  • @Björn Pollex 一针见血。间接层很多,要保证某些子函数不调用f。

标签: python recursion introspection inspect


【解决方案1】:

为此使用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

【讨论】:

    【解决方案2】:

    您可以使用装饰器设置的标志:

    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 并且会引发异常。

    【讨论】:

    • 这有一些不需要的内存:假设您的f 定义为:def f(func): print func()。如果你用f(f) 调用它,你会得到递归警告。如果你然后用f(lambda: 'foo') 调用它,你也会收到警告,虽然它没有递归......
    • 这在从多个线程调用函数的应用程序中将无法可靠地工作,尽管我猜它可能也可以通过使用 threading.local 作为布尔值的存储来适应在那里工作。
    • @jro 我认为这是出于调试目的,如果检测到不需要的递归,OP 不想继续执行。如果他确实想继续,可以很容易地在raiseing 之前添加func.called = False
    • @lazyr 如果问题中没有提到,为什么我会假设代码需要是线程安全的?
    • 我不是说你应该有。该评论旨在警告和建议在这种情况下可能使用此解决方案的人。 traceback 解决方案是线程安全的,这个不是,但可以做到。
    猜你喜欢
    • 2020-11-04
    • 1970-01-01
    • 2013-01-10
    • 1970-01-01
    • 1970-01-01
    • 2014-08-29
    • 1970-01-01
    • 2021-10-26
    • 1970-01-01
    相关资源
    最近更新 更多