I am aware of Why Python raises RecursionError before it exceeds the real recursion limit? question, but the answers there don't apply to following case:
from sys import getrecursionlimit
print(f'sys: maxRecursionDepth = {getrecursionlimit()}')
cnt = 0
def f3(s):
global cnt
cnt = 1
eval(s)
# ---
try:
f3('f3(s)')
except RecursionError:
print(f'f3() maxRecursionDepth = {cnt}')
which outputs:
sys: maxRecursionDepth = 1000
f3() maxRecursionDepth = 333
It seems that calling eval()
consumes two recursion levels before the counter in the recursive function is increased again.
I would like to know why it is that way? What is the reason for the observed behavior?
The answers in the link don't apply to the case above because they explain that the stack might be already used by other processes of Python and therefore the counter does not count up to the recursion limit. What is the difference between eval() and another functions which don't so massively fill the call stack? I would expect a 499 or similar as result, but not 333.
CodePudding user response:
The "recursion" limit doesn't actually care about recursion. It cares about how many stack frames you create. If you set it to something really low, you can trigger RecursionError
without recursion.
>>> import sys
>>> sys.setrecursionlimit(4)
>>> def a():
... b()
...
>>> def b():
... print("Never here")
...
>>> a()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in a
File "<stdin>", line 2, in b
RecursionError: maximum recursion depth exceeded while calling a Python object
In your case, it's the call to eval
at each step that causes you to fill the call stack sooner than you expect.
CodePudding user response:
The eval
calls is technically two stack levels:
- The execution of
eval
itself. - The execution of
eval
's argument.
While eval
is a builtin, the ()
-call does not know that ahead of time – it still needs a stack level for itself. The argument is executed as a separate statement with the given or implicit globals/locals, and thus also needs a stack level.
The sneaky part is that eval
prevents re-using the current call to f(3)
to execute the statement for its next call. The added top-level is visible when not suppressing RecursionError
:
...
File "/Users/mistermiyagi/so_testbed.py", line 7, in f3
eval(s)
File "<string>", line 1, in <module>
File "/Users/mistermiyagi/so_testbed.py", line 7, in f3
eval(s)
File "<string>", line 1, in <module>
...
Each File "<string>", line 1, in <module>
is a separate frame for the top-level execution. The eval
execution itself does not show up since it is a builtin call and thus has no Python frame, only a C stack level.