由于我们遇到的最大重现深度误差,我正在重构Python信号处理框架。
对错误最可能的解释是,有时会从类中递归地创建单个类的大量实例。它的 init 方法。事实上,模拟这种情况的实验会导致异常运行时错误:超过最大递归深度'。
当我将链中的下一个元素移动到管理这些对象的容器时,问题似乎消失了,尽管我在天真的理解中建立了相同深度的调用堆栈。我通过所有以前创建的对象调用create。 (参见示例代码)。
我倾向于得出结论,递归深度限制以某种方式设置为每个对象(无论是类还是实例)。当通过 init 递归创建时,可能会将所有内容放在类的堆栈中,就好像我们通过同一类的一行实例递归创建它们一样,所有对象的递归深度都非常有限。 / p>
我正在寻求对此假设的一些确认,但很难得到确认或反驳。
String.Format("{0}\t{1}\t{2}\t{3}\r\n", x.url, x.company, x.country, x.vendor, );
^^^
注意补充:
显示最大递归深度的代码:
import sys
class Chunk(object):
pre=None
post=None
def __init__(self, container,pre):
print 'Chunk Created'
self.pre=pre
self.container=container
def create(self,x):
if self.post == None:
self.post=Chunk(self.container,self)
newChunk =self.post
else:
newChunk =self.post.create(x+1)
return newChunk
def droprefs(self):
print 'refcount droprefs start: ', sys.getrefcount(self)
if not self.pre ==None:
self.pre.droprefs()
self.pre=None
self.post=None
self.container=None
print 'refcount droprefs end: ', sys.getrefcount(self)
def claimContainer(self):
self.container.setmasterchunk(self)
self.pre.droprefs()
self.pre=None
class Container(object):
masterchunk=None
def __init__(self):
self.masterchunk=Chunk(self, None)
def setmasterchunk(self, chunk):
print 'refcount 5: ', sys.getrefcount(self.masterchunk)
self.masterchunk=chunk
print 'refcount 6: ', sys.getrefcount(self.masterchunk)
def testrecursion(self,n):
for i in range(n):
print 'refcount 6: ', sys.getrefcount(self.masterchunk)
newChunk=self.masterchunk.create(1)
return newChunk
def testhistoryremoval(self,chunk):
chunk.claimContainer()
if __name__ == '__main__':
C=Container()
middle=C.testrecursion(300)
last=C.testrecursion(600)
last=C.testhistoryremoval(middle)
if middle== C.masterchunk:
print 'SUCCESS'
答案 0 :(得分:0)
不,它是一个全局递归深度。您描述的情况意味着当您超出堆栈限制时,您将捕获异常并继续下一个对象。此过程将删除关联的堆栈条目 - 将递归解开到其起始点。
你从下一个对象开始。如果这个没有超过堆栈深度,它将顺利完成 - 先前的堆栈条目不会影响新的堆栈条目。
答案 1 :(得分:0)
在我使用的实现中,设置
sys.setrecursionlimit(907)
表明这是你达到的递归深度。
对于你的问题,答案是否定的。在CPython的源代码中,您可以看到递归深度是每个线程,而不是每个对象。
typedef struct _ts {
/* See Python/ceval.c for comments explaining most fields */
//...
int recursion_depth;
//...
}
/* the macro Py_EnterRecursiveCall() only calls _Py_CheckRecursiveCall()
if the recursion_depth reaches _Py_CheckRecursionLimit.
If USE_STACKCHECK, the macro decrements _Py_CheckRecursionLimit
to guarantee that _Py_CheckRecursiveCall() is regularly called.
Without USE_STACKCHECK, there is no need for this. */
int
_Py_CheckRecursiveCall(const char *where)
{
PyThreadState *tstate = PyThreadState_GET();
//...
if (tstate->recursion_depth > recursion_limit) {
--tstate->recursion_depth;
tstate->overflowed = 1;
PyErr_Format(PyExc_RecursionError,
"maximum recursion depth exceeded%s",
where);
return -1;
}
//...
}
tstate
是线程状态的简写。
答案 2 :(得分:0)
您可以将方法重写为迭代而不是递归。无论数据结构的深度如何,这都可以避免递归深度误差的可能性。
create
方法会变成这样:
def create(self, x): # the `x` arg is not actually used for anything?
chunk = self
while chunk.post is not None:
chunk = chunk.post
chunk.post = Chunk(self.container, chunk)
return self.post # this matches the old code but you may actually want `return self.post`
您可以使用droprefs
执行类似操作。您当前的代码似乎从列表前进的末尾开始迭代,这可能是也可能不是您想要的。这是对迭代行为的直接转换:
def droprefs(self):
chunk = self
while chunk:
next = chunk.pre # this iterates backwards, to go forwards use `next = chunk.post`
chunk.pre = None
chunk.post = None
chunk.container = None
chunk = next
我还要注意,除非您希望外部代码能够保留对早期Chunk
的引用,否则您可能根本不需要使用droprefs
。在claimContainer
执行self.pre = None
之后,垃圾收集器应该能够清理所有早期的Chunk
实例,因为不再有外部引用它们。摆脱彼此的引用可能会使其更快地工作(因为pre
和post
属性构成了引用周期),但并不是绝对必要的。