10

To start I tried this

def x():
   try:
      1/0 # just an division error to get an exception
   except:
      x()

And this code behaves normally in 3.10 and I get RecursionError: maximum recursion depth exceeded as I expected but 3.8 goes into a stack overflow and doesn't handle the recursion error properly. But I did remember that there was RecursionError in older versions of Python too, so I tried

def x(): x()

And this gives back RecursionError in both versions of Python.

It's as if (in the first snippet) the recursion error is never thrown in the except but the function called and then the error thrown at the first instruction of the function called but handled by the try-except.

I then tried something else:

def x():
   try:
      x()
   except:
      x()

This is even weirder in some way, stack overflow below 3.10 but it get stuck in the loop in 3.10

Can you explain this behavior?

UPDATE @MisterMiyagi found a even stranger behavior, adding a statement in the except in <=python3.9 doesn't result in a stackoverflow

def x():
   try:
      1/0
   except:
      print("")
      x()
sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
Axeltherabbit
  • 680
  • 3
  • 20
  • 5
    In the last case it pops the top stack frame of when handling the error so there is space for another recursion. Thus it will forever loop between the last 2 recursion levels. – mousetail Oct 10 '22 at 09:19
  • As for the first example it's probably a bug – mousetail Oct 10 '22 at 09:22
  • Why? I'd aspect the same behavior as the first in the last : in-try-call fails then in-except-call which fails finally throw a RecursionError – Axeltherabbit Oct 10 '22 at 09:27
  • 1
    Yea but it will take a very very long time, specifically 2 ** sys.getrecursionlimit(). – mousetail Oct 10 '22 at 09:28
  • 3
    Specifically, there will be a total of 10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376 function calls with the default recursion limit of 1000. – mousetail Oct 10 '22 at 09:29
  • Why? isn't it the same stack? – Axeltherabbit Oct 10 '22 at 09:33
  • No, when a recursion error is raised inside the except block the function will exit popping one frame from the stack. Now there are 999 frames on the stack. Then the except block in the calling function will call `x()` again bringing the number of stack frames back up to 1000. – mousetail Oct 10 '22 at 09:35
  • ok, but why would that be 2^1000 then? I don't get your maths here, one call fails, another one is done in the same stack – Axeltherabbit Oct 10 '22 at 09:37
  • Because each stack frame calls `x()` twice – mousetail Oct 10 '22 at 09:40
  • 1
    Try setting the recursion limit way lower (something like 5) then adding some print statements to `x` and see what happens – mousetail Oct 10 '22 at 09:40
  • I tried it but I don't get the behavior of setrecursionlimit now, with a limit of 5 it crashes at a depth of 1,6 at a depth of 4, with a limit of 10 at the depth of 64, I don't see anything useful in the docs – Axeltherabbit Oct 10 '22 at 09:56
  • Does your first code fail „as expected“ on 3.8 when you explicitly set a low recursion limit? A RecursionError and Stackoverflow are related but ultimately separate error conditions. – MisterMiyagi Oct 13 '22 at 12:58
  • @MisterMiyagi, not sure what you mean, the first doesn't throw RecursionError as it should in `<=3.9`, I have not set any recursion limit, just the default – Axeltherabbit Oct 13 '22 at 13:04
  • @Axeltherabbit That is why I am asking whether using a non-default recursion limit changes things. The default is basically a guess, and since your code is highly pathological the guess may just be wrong. – MisterMiyagi Oct 13 '22 at 13:05
  • 1
    @MisterMiyagi same result with sys.setrecursionlimit(10) – Axeltherabbit Oct 13 '22 at 13:38
  • 1
    This appears to be rather spurious. I can repro this on 3.8 (`Fatal Python error: Cannot recover from stack overflow.`) and 3.10 (`RecursionError: maximum recursion depth exceeded`). *But* small modifications behave unexpectedly on 3.8: `except ZeroDivisionError` -> `RecursionError:` but `except BaseException:` -> `Fatal Python error:`. Just adding `print("")` inside the bare `except:` is enough to avoid the stack overflow... – MisterMiyagi Oct 13 '22 at 15:34
  • oh wow, yes it doesn't crash when you add a statement in except, super weird – Axeltherabbit Oct 13 '22 at 15:51

1 Answers1

5

The different behaviors for 3.10 and other versions seem to be because of a Python issue (python/cpython#86666), you can also see the correct error on Python 2.7.

The print "fixes" things because it makes Python check the recursion limit again, and through a path that is presumably not broken. You can see the code where it does that here, it also skips the repeated check if the object supports the Vectorcall calling protocol, so things like int keep the fatal error.

Numerlor
  • 799
  • 1
  • 3
  • 16