Is python maximum recurrence depth per object?
我正在重构一个python信号处理框架,因为我们遇到了一个最大的重复深度错误。
对于该错误最可能的解释是,有时单个类的大量实例是从类的init方法递归创建的。实际上,模拟这种情况的实验会导致一个异常的runtimeerror:"超过了最大递归深度"。
当我将创建链中的下一个元素移动到容器管理这些对象时,问题似乎消失了,尽管在我幼稚的理解中构建了相同深度的调用堆栈。我通过以前创建的所有对象调用create。(参见示例代码)。
我倾向于得出这样的结论:递归深度限制是以某种方式为每个对象设置的(无论是类还是实例)。当通过init递归创建时,可能会将所有内容都放在类的堆栈上,在这里,就像我们通过同一类的一行实例递归创建它们一样,所有对象的递归深度都非常有限。
我正在为这个假设寻求一些证实,但发现很难得到证实或反驳。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | 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' |
添加的注释:
显示最大递归深度的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | import sys from time import sleep class Chunk(object): pre=None post=None def __init__(self, container,pre,n): print 'Chunk Created' self.pre=pre self.container=container if n>0: self.post=Chunk(self.container,self,n-1) 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): pass 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): self.masterchunk=Chunk(self,None,n) if __name__ == '__main__': print('Try 10') C0=Container() C0.testrecursion(10) sleep(2) print('Try 1000') C1=Container() C1.testrecursion(1000) |
号
您可以将方法重写为迭代的,而不是递归的。这避免了递归深度错误的可能性,不管数据结构有多深。
1 2 3 4 5 6 7 8 9 | 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` |
你可以用
1 2 3 4 5 6 7 8 9 10 11 | 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 |
我还注意到,除非您希望外部代码包含对早期
在我使用的实现中,设置
1 | sys.setrecursionlimit(907) |
演示这是您达到的递归深度。
对于您的问题,答案是否定的。在cpython的源代码中,您可以看到递归深度是每个线程,而不是每个对象。
PySt.H:
1 2 3 4 5 6 7 | typedef struct _ts { /* See Python/ceval.c for comments explaining most fields */ //... int recursion_depth; //... } |
C:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* 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; } //... } |
不,这是全局递归深度。您描述的情况意味着当您超过堆栈限制时,您捕获异常并继续处理下一个对象。这个过程删除相关的堆栈项——将递归分解到其起点。
从下一个对象开始。如果这个堆栈没有超过堆栈深度,它将顺利完成——先前的堆栈条目不会影响新的堆栈。