Почему функция, которая возвращает себя max из рекурсии в python 3
Почему этот код дает ошибку: RuntimeError: maximum recursion depth exceeded during compilation
? print_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():
Мне интересно это из любопытства, и поймите, что это не будет лучшая практика программирования.
Ответы
Ответ 1
Я считаю, что это связано с Issue # 5765.
Примените ограничение жесткой рекурсии в компиляторе [начиная с 3.3]
Не 100% уверен, но этот код работает на 3.2.3:
def f():
return f
eval("f" + "()" * 10000)
но сбой на моем 3.4.1, который оставляет меня подозревать, что это изменение вызвало это. Если кто-то подтвердит или опровергнет это, это будет довольно круто.
Ответ 2
eval
выполняет компиляцию. Он может обнаружить, что вы хотите глубже входить в вложенные вызовы, чем sys.getrecursionlimit()
.
Рекурсия может быть как прямой (функция вызывает непосредственно), либо косвенной (функция вызывается другой функцией, которая в свою очередь была однажды вызвана этой функцией). Но когда глубина вызова больше ожидаемой (1000 для реализации Python на момент написания), вызов может быть частью косвенной рекурсии, которая требует еще более глубокого, чтобы проявить себя как рекурсивный. Другими словами, если максимальная глубина вызова достигнута (и глубина разумна, чтобы думать таким образом), это хорошо, чтобы назвать ее рекурсией, которая не может быть выполнена.
Окончательный ответ можно найти в источниках... Но мне было просто любопытно, поэтому я сделал следующий эксперимент. Я попытался написать код, который определяет достаточное количество различных функций (с пронумерованными идентификаторами), которые вызывают следующий, за исключением последнего, который на самом деле не вызывает рекурсивно первый.
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
it:
#!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
в начале источника. Когда исполняемый файл и stdout и stderr перенаправлены на файлы, я мог бы заметить:
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
, когда он является рекурсией, но еще не выполнен (как показывает вопрос). Python может называть его рекурсией даже в случае, когда на самом деле нет рекурсии.
Лучшее заключение: Кто заботится, когда ясно, что код может быть рекурсией или нет, во время компиляции или во время выполнения. В любом случае это не сработает:)