【问题标题】:Timeout for recursion Python递归Python超时
【发布时间】:2012-07-17 17:59:00
【问题描述】:

我有一个脚本,它递归地分析链接中的 javascript,所以如果它找到一个 javascript,那么它会分析 javascript,如果它正在分析的 javascript 包含更多的 javascript,那么它会继续运行,依此类推。但是,我遇到了这个递归永远不会停止的问题,有没有办法为这个递归添加超时?

【问题讨论】:

  • 愿意分享一些代码吗?

标签: python recursion timeout


【解决方案1】:

Python 有一个内置的递归限制,如果超过这个限制,将引发RuntimeError。默认情况下,它的堆栈限制为 1000。所以:

try:
    func_that_may_recurse_infinitely()   # i.e., your JavaScript crawler func
except RuntimeError as e:
    if "recursion" in str(e):
        print "stop all the downloadin'!"

如果您需要更深或更浅,您可以使用sys.setrecursionlimit() 修改初始递归限制。

但是,更好的方法可能是保留您已查看的项目的set(),并拒绝处理您已处理的任何项目。这可以防止您首先陷入递归情况。

【讨论】:

  • 哦,这个很有用。但我认为出于我的目的,我更喜欢深度版本。谢谢!
  • memoization 的想法很简洁,但在这里不一定有用。如果无限递归的原因是JS实际上是在对自己进行评估,那很好;如果递归的原因是 JS 每次都在评估一个稍长的 JS 字符串,它仍然会永远存在。
  • 因此是“可能”。我不知道“分析”包括所以我不知道它对于手头的问题有多实用。
  • @abarnert 是的,这正是问题所在,我正在处理的特定示例中的 js 相互引用,导致无限递归。
  • 好吧,如果第一个 eval 正好是第二个,第二个正好是第一个,那么 kindall 建议的技巧将能够检测到循环并在 2 个步骤后退出,而不必循环 N 次或 N 秒 - 不仅节省大量时间和 CPU,还意味着您可以确定它确实是一个无限循环,而不是一个有限的循环,它对于您的限制来说太深了。跨度>
【解决方案2】:

我倾向于同意kindall。但是,如果您确实想限制递归的深度,则可以执行以下操作:

def foo(max_depth = 10, cur_depth=0):
    if cur_depth >= max_depth:
        return BASE_CASE

    else:
        return foo(max_depth, cur_depth+1)

【讨论】:

  • 哦,这很有用!谢谢!
  • 哦,问题,if depth >=max_depth,我假设你的意思是 cur_depth?
【解决方案3】:

一个快速而简单的解决方案是调用 time.time() 并进行比较。例如,假设您有一个简单的阶乘函数,如下所示:

def fact(i):
  if i == 0 or i == 1: return 1
  return i * fact(i-1)

如果你调用 fact(-1),它会旋转一段时间,然后由于最大递归深度而引发 RuntimeError。

您可以像这样添加超时:

import time

def factWithTimeout(i, timeout):
  def fact(i, endtime):
    if time.time() > endtime:
      raise RuntimeError('Timeout')
    if i == 0 or i == 1: return 1
    return i * fact(i-1, endtime)
  return fact(i, time.time() + timeout)

现在,如果你调用factWithTimeout(-1, 0.0001),它只会旋转大约 100us,然后因为超时而退出 RuntimeError。

显然,对于一个如此微不足道的函数,它会在一毫秒内达到递归限制,这并没有太大的不同,但对于一个更现实的函数,这将不是问题。

【讨论】:

    【解决方案4】:

    你可以这样做:

    import time
    
    start = time.time()
    timeout_limit = 30   # 30 seconds, or some other number.
    
    def foo():
        if time.time() > start + timeout_limit:
            return 0
    
        # insert code here.
    
        foo()
    

    ...如果你不想要全局变量,你可以试试这个:

    class Foo(object):
        def __init__(self, timeout_limit):
            self.timeout_limit = timeout_limit
    
        def run(self, ...):
            self.start = time.time()
            self._run(...)
    
        def _run(self, ...):
            if time.time() > self.start + self.timeout_limit:
                return
    
            # insert code here.
    
            self._run(...)
    

    ...虽然这可能有点矫枉过正。

    【讨论】:

    • 是的,这比我预想的要复杂一些。
    • 在这种情况下,我认为使用本地函数和闭包(如我的回答)比使用对象更有意义,因为状态是如此微不足道,并且不需要接口用多种方法。但这种方式更明确,尤其是对于那些拥有更多 OO 而非 FP 背景的人。
    猜你喜欢
    • 1970-01-01
    • 2022-01-21
    • 2020-06-25
    • 2019-03-12
    • 2013-04-06
    • 1970-01-01
    • 2016-01-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多