I have a code with a one-liner while
and a try
-except
statement which behaves weirdly.
This prints 'a' on Ctrl+C:
try:
while True:
pass
except KeyboardInterrupt:
print("a")
and this too:
try:
i = 0
while True: pass
except KeyboardInterrupt:
print("a")
but this doesn't, and it throws a traceback:
try:
while True: pass
except KeyboardInterrupt:
print("a")
and neither does this code:
try:
while True: pass
i = 0
except KeyboardInterrupt:
print("a")
Addition some additional details.
In 3.11, the instruction JUMP_BACKWARD
was added and seems invloved with this issue see: Disassembler for Python bytecode
In 3.12 when the code in the first and the 3rd blocks are disassembled the results are:
Cannot be caught:
0 0 RESUME 0
2 2 NOP
3 >> 4 JUMP_BACKWARD 1 (to 4)
>> 6 PUSH_EXC_INFO
4 8 LOAD_NAME 0 (KeyboardInterrupt)
10 CHECK_EXC_MATCH
12 POP_JUMP_IF_FALSE 11 (to 36)
14 POP_TOP
5 16 PUSH_NULL
18 LOAD_NAME 1 (print)
20 LOAD_CONST 1 ('a')
22 CALL 1
30 POP_TOP
32 POP_EXCEPT
34 RETURN_CONST 2 (None)
4 >> 36 RERAISE 0
>> 38 COPY 3
40 POP_EXCEPT
42 RERAISE 1
ExceptionTable:
4 to 4 -> 6 [0]
6 to 30 -> 38 [1] lasti
36 to 36 -> 38 [1] lasti
None
Can be caught:
0 0 RESUME 0
2 2 NOP
3 4 NOP
4 >> 6 NOP
3 8 JUMP_BACKWARD 2 (to 6)
>> 10 PUSH_EXC_INFO
5 12 LOAD_NAME 0 (KeyboardInterrupt)
14 CHECK_EXC_MATCH
16 POP_JUMP_IF_FALSE 11 (to 40)
18 POP_TOP
6 20 PUSH_NULL
22 LOAD_NAME 1 (print)
24 LOAD_CONST 1 ('a')
26 CALL 1
34 POP_TOP
36 POP_EXCEPT
38 RETURN_CONST 2 (None)
5 >> 40 RERAISE 0
>> 42 COPY 3
44 POP_EXCEPT
46 RERAISE 1
ExceptionTable:
4 to 8 -> 10 [0]
10 to 34 -> 42 [1] lasti
40 to 40 -> 42 [1] lasti
None
The main differences that jump out are the two additional NOP
and the different targets for JUMP_BACKWARD
.
Note: the exception really cannot be caught as this will also throw the exception in 3.12
try:
try:
while True: pass
except KeyboardInterrupt:
print("a")
except Exception:
print("b")
Its a known CPython bug introduced in 3.11
and exists in 3.12
.
One of comments of the bug, mentioned that partial backport of this pull request looks to be the right direction to fix the bug.
I built and tested following CPython versions from source using pyenv on Arch Linux with GCC 14.1.1 compiler:
3.11-dev
: Python 3.11.9+ (heads/3.11:ba43157, May 20 2024, 04:40:02)
3.12-dev
: Python 3.12.3+ (heads/3.12:30c687c, May 20 2024, 04:38:13)
3.13.0b1
: Python 3.13.0b1 (main, May 20 2024, 04:14:35)
3.13-dev
: Python 3.13.0b1+ (heads/3.13:27b61c1, May 20 2024, 04:24:49)
3.14-dev
: Python 3.14.0a0 (heads/main:0abf997, May 20 2024, 08:25:05)
In 3.13.0b1
, 3.13-dev
and 3.14-dev
the bug is fixed 😀👍 and exception handling works as expected.
But 3.11-dev
and 3.12-dev
still have the bug.
I hope it will be backport to existing stable 3.11
and 3.12
versions (in-time for inclusion in the next 3.11.10
and 3.12.4
bug-fix releases respectively).
EDIT 1: 3.12.4
released in 2024-06-06: the bug didn't fixed.
EDIT 2: 3.12.5
released in 2024-08-06: the bug didn't fixed.