【问题标题】:Why does a function that returns itself max out recursion in python 3为什么在python 3中返回自身最大递归的函数
【发布时间】:2014-08-11 20:28:37
【问题描述】:

为什么这段代码会给出错误:RuntimeError: maximum recursion depth exceeded during compilationprint_test 从不调用自己,因此我认为它不是递归函数。

def print_test():
    print("test")
    return print_test

print_test() #prints 'test'
print()

#a quick way of writing "print_test()()()()()()()()()()()()()..."
eval("print_test"+"()"*10000) #should print 'test' 10000 times

当我测试它时,它在 Python 2.7.7rc1 中工作,但在 Python 3.3.5 中出现错误。 pdb 提供一个短调用堆栈,与超过最大递归深度时通常存在的高堆栈不同。

Traceback (most recent call last):
  File "/usr/lib/python3.3/pdb.py", line 1662, in main
    pdb._runscript(mainpyfile)
  File "/usr/lib/python3.3/pdb.py", line 1543, in _runscript
    self.run(statement)
  File "/usr/lib/python3.3/bdb.py", line 405, in run
    exec(cmd, globals, locals)
  File "<string>", line 1, in <module>
  File "/home/beet/overflow.py", line 1, in <module>
    def print_test():

出于好奇,我想知道这一点,并意识到这不是最佳编程实践。

【问题讨论】:

  • 在您的脚本上运行 pdb 是否会告诉您任何信息?
  • 编译期间超出,而不是执行。该函数没有破坏堆栈;您在字节码编译器中遇到了某种限制。

标签: python python-3.x recursion


【解决方案1】:

我相信这与Issue #5765有关。

在编译器中应用硬递归限制 [自 3.3 起]

不是 100% 确定,但此代码 runs on 3.2.3

def f():
    return f

eval("f" + "()" * 10000)

但在我的 3.4.1 上失败了,这让我怀疑是这种变化引起的。如果有人会确认或否认这一点,那就太酷了。

【讨论】:

    【解决方案2】:

    eval 进行编译。它可以发现你想要比sys.getrecursionlimit()更深入的嵌套调用。

    递归既可以是直接的(函数直接调用自身),也可以是间接的(函数被另一个函数调用,而另一个函数又被这个函数调用过一次)。但是,当调用深度大于预期时(撰写本文时 Python 实现为 1000),调用可能是间接递归的一部分,需要更深入地表明自己是 recursive。换句话说,如果达到了最大调用深度(并且深度这样认为是合理的),则将其命名为无法完成的递归

    确切的答案可以在资料中找到……但我只是好奇,所以我做了以下实验。我试图编写代码来定义足够多的不同函数(带有编号的标识符)来调用下一个函数——除了实际上递归调用第一个函数的最后一个函数。

    def fn1():
        print('fn1() is going to call fn2()')
        fn2()
    
    def fn2():
        print('fn2() is going to call fn3()')
        fn3()
    
    def fn3():
        print('fn3() does not call anything.')
    
    fn1()
    

    最后一行开始嵌套调用并打印出来。

    fn1() is going to call fn2()
    fn2() is going to call fn3()
    fn3() does not call anything.
    

    我们可以将源生成为字符串,然后我们可以使用compile内置函数然后exec它:

    #!python3
    import textwrap
    
    n = 1000
    print('n =', n)
    
    lst = []
    for i in range(1, n+1):
        if i == n:
            fndef = textwrap.dedent('''\
                def fn{}():
                    print('fn{}() does not call anything.')
    
                fn1()
                '''.format(i, i))
        else:
            fndef = textwrap.dedent('''\
                def fn{}():
                    print('fn{}() is going to call fn{}()')
                    fn{}()
                '''.format(i, i, i+1, i+1))
    
        if n <= 10:
            print(fndef)
        lst.append(fndef)
    
    ast = compile('\n'.join(lst), '<string>', 'exec')
    exec(ast)
    

    注意源代码开头的n = 1000。当执行并将标准输出和标准错误重定向到文件时,我可以观察到:

    n = 1000
    fn1() is going to call fn2()
    fn2() is going to call fn3()
    fn3() is going to call fn4()
    ...
    fn992() is going to call fn993()
    fn993() is going to call fn994()
    fn994() is going to call fn995()
    
    Traceback (most recent call last):
      File "a.py", line 28, in <module>
        exec(ast)
      File "<string>", line 4000, in <module>
      File "<string>", line 3, in fn1
      File "<string>", line 7, in fn2
      File "<string>", line 11, in fn3
      ...
      File "<string>", line 3967, in fn992
      File "<string>", line 3971, in fn993
      File "<string>", line 3975, in fn994
      File "<string>", line 3978, in fn995
    RuntimeError: maximum recursion depth exceeded
    

    结论:Python 不仅在eval 调用它recursion,当它是递归但它还没有被执行时(如问题所示)。即使在实际上没有递归的情况下,Python 也可以将其称为 recursion

    更好的结论:在编译期间或运行时,何时清楚代码是否是递归,谁在乎。无论如何它都行不通:)

    【讨论】:

      猜你喜欢
      • 2021-12-23
      • 1970-01-01
      • 2013-11-04
      • 2021-11-13
      相关资源
      最近更新 更多