每个对象的python最大重复深度是多少?

时间:2016-08-04 22:03:31

标签: python recursion stack

由于我们遇到的最大重现深度误差,我正在重构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'

3 个答案:

答案 0 :(得分:0)

不,它是一个全局递归深度。您描述的情况意味着当您超出堆栈限制时,您将捕获异常并继续下一个对象。此过程将删除关联的堆栈条目 - 将递归解开到其起始点。

你从下一个对象开始。如果这个没有超过堆栈深度,它将顺利完成 - 先前的堆栈条目不会影响新的堆栈条目。

答案 1 :(得分:0)

在我使用的实现中,设置

sys.setrecursionlimit(907)

表明这是你达到的递归深度。

对于你的问题,答案是否定的。在CPython的源代码中,您可以看到递归深度是每个线程,而不是每个对象。

pystate.h

typedef struct _ts {
    /* See Python/ceval.c for comments explaining most fields */

    //...
    int recursion_depth;
    //...
}

ceval.c

/* 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实例,因为不再有外部引用它们。摆脱彼此的引用可能会使其更快地工作(因为prepost属性构成了引用周期),但并不是绝对必要的。