Python:调用 Python 对象时超出了最大递归深度
Posted
技术标签:
【中文标题】Python:调用 Python 对象时超出了最大递归深度【英文标题】:Python: maximum recursion depth exceeded while calling a Python object 【发布时间】:2011-10-12 04:05:58 【问题描述】:我已经构建了一个爬虫,它必须在大约 500 万个页面上运行(通过增加 url ID)然后解析包含我需要的信息的页面。
使用在 url (200K) 上运行的算法并保存好结果和坏结果后,我发现我浪费了很多时间。我可以看到有一些返回的减数可以用来检查下一个有效的 url。
你可以很快看到减数(前几个“好 ID”的小例子)-
510000011 # +8
510000029 # +18
510000037 # +8
510000045 # +8
510000052 # +7
510000060 # +8
510000078 # +18
510000086 # +8
510000094 # +8
510000102 # +8
510000110 # etc'
510000128
510000136
510000144
510000151
510000169
510000177
510000185
510000193
510000201
在爬取了大约 200K 的 url 后,我只得到了 14K 的好结果,我知道我在浪费时间并且需要优化它,所以我运行了一些统计数据并构建了一个函数来检查 url,同时将 id 增加 8\18 \17\8(顶部返回减数)等'。
这是函数 -
def checkNextID(ID):
global numOfRuns, curRes, lastResult
while ID < lastResult:
try:
numOfRuns += 1
if numOfRuns % 10 == 0:
time.sleep(3) # sleep every 10 iterations
if isValid(ID + 8):
parsehtml(curRes)
checkNextID(ID + 8)
return 0
if isValid(ID + 18):
parseHTML(curRes)
checkNextID(ID + 18)
return 0
if isValid(ID + 7):
parseHTML(curRes)
checkNextID(ID + 7)
return 0
if isValid(ID + 17):
parseHTML(curRes)
checkNextID(ID + 17)
return 0
if isValid(ID+6):
parseHTML(curRes)
checkNextID(ID + 6)
return 0
if isValid(ID + 16):
parseHTML(curRes)
checkNextID(ID + 16)
return 0
else:
checkNextID(ID + 1)
return 0
except Exception, e:
print "somethin went wrong: " + str(e)
基本上做的是 -checkNextID(ID) 正在获取我知道的第一个 id,其中包含减 8 的数据,因此第一次迭代将匹配第一个“if isValid”子句(isValid(ID + 8) 将返回 True) .
lastResult 是保存最后一个已知 url id 的变量,所以我们会一直运行到 numOfRuns 为
isValid() 是一个函数,它获取一个 ID + 一个减数,如果 url 包含我需要的内容,则返回 True 并将 url 的汤对象保存到名为 - 的全局变量中curRes',如果 url 不包含我需要的数据,则返回 False。
parseHTML 是一个函数,它获取汤对象 (curRes),解析我需要的数据,然后将数据保存到 csv,然后返回 True。
如果 isValid() 返回 True,我们将调用 parseHTML() 然后尝试检查下一个 ID+被减数(通过调用 checkNextID(ID + subtrahends),如果它们都不会返回我正在寻找的内容我将其增加 1 并再次检查,直到找到下一个有效 url。
你可以看到剩下的代码here
运行代码后我得到了大约 950~ 好的结果,突然引发了一个异常 -
“出了点问题:调用 a 时超出了最大递归深度 Python 对象”
我可以在 WireShark 上看到 scipt 卡在 id - 510009541 上(我以 510000003 开始我的脚本),在我注意到错误并停止它之前,脚本尝试了几次使用该 ID 获取 URL。
看到我得到了相同的结果,但比我的旧脚本快 25-40 倍,HTTP 请求更少,非常精确,我真的很兴奋,我只错过了 1000 个好的结果,这是由我,5M 次朗姆酒是不可能的,我的旧脚本运行了 30 个小时,得到了 14-15K 的结果,而我的新脚本在 5-10 分钟内给了我 960~ 个结果。
我阅读了有关堆栈限制的信息,但我试图在 Python 中实现的算法必须有一个解决方案(我不能回到我的旧 “算法”,它永远不会结束)。
谢谢!
【问题讨论】:
每个递归算法都可以转换为等效的迭代算法,最简单的方法是在算法级别处理堆栈(例如,将节点推入堆栈,而不是在深度优先树遍历中对其进行递归) ,有时还有一种更简单(更自然)的迭代算法可以做同样的事情。 Thomas K,请见谅,我还在学习使用 ***,我会复习我得到的所有答案。 【参考方案1】:Python 没有很好的递归支持,因为它缺少 TRE (Tail Recursion Elimination)。
这意味着对递归函数的每次调用都会创建一个函数调用堆栈,并且由于堆栈深度有一个限制(默认为 1000),您可以通过sys.getrecursionlimit
进行检查(当然您可以使用sys.setrecursionlimit 但不推荐)你的程序在达到这个限制时会崩溃。
由于其他答案已经为您提供了一种更好的方法来解决您的情况(即通过简单循环替换递归),如果您仍想使用递归,则还有另一种解决方案,即使用其中一个许多在 python 中实现 TRE 的秘诀就像 one.
注意:我的回答旨在让您更深入地了解为什么会出现错误,我不建议您像我已经解释过的那样使用 TRE,因为在您的情况下,循环会更好,更易于阅读。
【讨论】:
【参考方案2】:您可以通过以下方式增加堆栈的容量:
import sys
sys.setrecursionlimit(10000)
【讨论】:
我有一个相当不错的 27 iInch iMac,这导致它被Bus Error: 10
阻塞,Python 退出了我
如果您无法控制递归部分,这是一个很好的解决方案。在这种情况下,您可以尝试将递归限制设置为更高的值。它对我有用。【参考方案3】:
这会将递归变成一个循环:
def checkNextID(ID):
global numOfRuns, curRes, lastResult
while ID < lastResult:
try:
numOfRuns += 1
if numOfRuns % 10 == 0:
time.sleep(3) # sleep every 10 iterations
if isValid(ID + 8):
parseHTML(curRes)
ID = ID + 8
elif isValid(ID + 18):
parseHTML(curRes)
ID = ID + 18
elif isValid(ID + 7):
parseHTML(curRes)
ID = ID + 7
elif isValid(ID + 17):
parseHTML(curRes)
ID = ID + 17
elif isValid(ID+6):
parseHTML(curRes)
ID = ID + 6
elif isValid(ID + 16):
parseHTML(curRes)
ID = ID + 16
else:
ID = ID + 1
except Exception, e:
print "somethin went wrong: " + str(e)
【讨论】:
我认为也应该像我在递归中那样调用 isValid(ID + 1),所以我也会检查 ID+1。 else: if isValid(ID + 1): parseHTML(curRes) ID = ID + 1 也许吧,但是那个检查没有出现在你的代码中,所以我没有添加它。 检查我的意思是isValid(ID+1)
,它没有出现在你的代码中;循环末尾的checkNextID(ID + 1)
与ID=ID+1; continue
相同,但continue
是多余的,所以我将其替换为ID = ID + 1
【参考方案4】:
您可以增加递归深度和线程堆栈大小。
import sys, threading
sys.setrecursionlimit(10**7) # max depth of recursion
threading.stack_size(2**27) # new thread will get stack of such size
【讨论】:
【参考方案5】:不用递归,checkNextID(ID + 18)
和类似代码的部分可以替换为ID+=18
,然后如果你删除return 0
的所有实例,那么它应该做同样的事情,但很简单环形。然后,您应该在末尾添加 return 0
并使您的变量成为非全局变量。
【讨论】:
以上是关于Python:调用 Python 对象时超出了最大递归深度的主要内容,如果未能解决你的问题,请参考以下文章
从 n = 8 开始的错误:“调用 Python 对象时超出了最大递归深度”[重复]
RecursionError:重命名列条目后调用 Python 对象时超出最大递归深度
PyQt4 python中的“RuntimeError:调用Python对象时超出最大递归深度”错误