These are changes I'm seeing in CPython 3.10 as a result of the PEP 626 changes.
The code comes from the coverage.py test suite. Numbers used in the code are often the line number.
Unreachable lines after break/continue used to be compiled. Coverage.py would report them as runnable but not run. Now they will appear as not runnable.
for i in range(10): a = i break # 3 a = 99 assert a == 0 # 5
Python 3.9 dis | Python 3.10 dis |
1 0 LOAD_NAME 0 (range) 2 LOAD_CONST 0 (10) 4 CALL_FUNCTION 1 6 GET_ITER >> 8 FOR_ITER 16 (to 26) 10 STORE_NAME 1 (i) 2 12 LOAD_NAME 1 (i) 14 STORE_NAME 2 (a) 3 16 POP_TOP 18 JUMP_ABSOLUTE 26 4 20 LOAD_CONST 1 (99) 22 STORE_NAME 2 (a) 24 JUMP_ABSOLUTE 8 5 >> 26 LOAD_NAME 2 (a) 28 LOAD_CONST 2 (0) 30 COMPARE_OP 2 (==) 32 POP_JUMP_IF_TRUE 38 34 LOAD_ASSERTION_ERROR 36 RAISE_VARARGS 1 >> 38 LOAD_CONST 3 (None) 40 RETURN_VALUE |
1 0 LOAD_NAME 0 (range) 2 LOAD_CONST 0 (10) 4 CALL_FUNCTION 1 6 GET_ITER 8 FOR_ITER 10 (to 20) 10 STORE_NAME 1 (i) 2 12 LOAD_NAME 1 (i) 14 STORE_NAME 2 (a) 3 16 POP_TOP 18 JUMP_ABSOLUTE 20 5 >> 20 LOAD_NAME 2 (a) 22 LOAD_CONST 2 (0) 24 COMPARE_OP 2 (==) 26 POP_JUMP_IF_TRUE 32 28 LOAD_ASSERTION_ERROR 30 RAISE_VARARGS 1 >> 32 LOAD_CONST 3 (None) 34 RETURN_VALUE |
Affected tests:
- LoopArcTest.test_break
- LoopArcTest.test_continue
# line 1 a = 2 b = 3 # line 4 c = 5
Python 3.9 show_pyc | Python 3.10 show_pyc |
code name '<module>' argcount 0 nlocals 0 stacksize 1 flags 0040: CO_NOFREE code 64005a0064015a0164025a0264035300 2 0 LOAD_CONST 0 (2) 2 STORE_NAME 0 (a) 3 4 LOAD_CONST 1 (3) 6 STORE_NAME 1 (b) 5 8 LOAD_CONST 2 (5) 10 STORE_NAME 2 (c) 12 LOAD_CONST 3 (None) 14 RETURN_VALUE consts 0: 2 1: 3 2: 5 3: None names ('a', 'b', 'c') varnames () freevars () cellvars () filename 'simple_sequence.py' firstlineno 2 lnotab 04010402 |
code name '<module>' argcount 0 nlocals 0 stacksize 1 flags 0040: CO_NOFREE code 64005a0064015a0164025a0264035300 2 0 LOAD_CONST 0 (2) 2 STORE_NAME 0 (a) 3 4 LOAD_CONST 1 (3) 6 STORE_NAME 1 (b) 5 8 LOAD_CONST 2 (5) 10 STORE_NAME 2 (c) 12 LOAD_CONST 3 (None) 14 RETURN_VALUE consts 0: 2 1: 3 2: 5 3: None names ('a', 'b', 'c') varnames () freevars () cellvars () filename 'simple_sequence.py' firstlineno 1 lnotab 000104010402 |
Affected tests:
- SimpleArcTest.test_simple_sequence
- LoopArcTest.test_multiline_dict_comp
This code jumps from line 3 to 5 to 7 even though no exception happens:
a, b, c = 1, 1, 1 try: a = 3 except: b = 5 finally: c = 7 assert a == 3 and b == 1 and c == 7
Python 3.9 dis | Python 3.10 dis |
1 0 LOAD_CONST 0 ((1, 1, 1)) 2 UNPACK_SEQUENCE 3 4 STORE_NAME 0 (a) 6 STORE_NAME 1 (b) 8 STORE_NAME 2 (c) 2 10 SETUP_FINALLY 34 (to 46) 12 SETUP_FINALLY 8 (to 22) 3 14 LOAD_CONST 1 (3) 16 STORE_NAME 0 (a) 18 POP_BLOCK 20 JUMP_FORWARD 16 (to 38) 4 >> 22 POP_TOP 24 POP_TOP 26 POP_TOP 5 28 LOAD_CONST 2 (5) 30 STORE_NAME 1 (b) 32 POP_EXCEPT 34 JUMP_FORWARD 2 (to 38) 36 RERAISE >> 38 POP_BLOCK 7 40 LOAD_CONST 3 (7) 42 STORE_NAME 2 (c) 44 JUMP_FORWARD 6 (to 52) >> 46 LOAD_CONST 3 (7) 48 STORE_NAME 2 (c) 50 RERAISE 8 >> 52 LOAD_NAME 0 (a) 54 LOAD_CONST 1 (3) 56 COMPARE_OP 2 (==) 58 POP_JUMP_IF_FALSE 76 60 LOAD_NAME 1 (b) 62 LOAD_CONST 4 (1) 64 COMPARE_OP 2 (==) 66 POP_JUMP_IF_FALSE 76 68 LOAD_NAME 2 (c) 70 LOAD_CONST 3 (7) 72 COMPARE_OP 2 (==) 74 POP_JUMP_IF_TRUE 80 >> 76 LOAD_ASSERTION_ERROR 78 RAISE_VARARGS 1 >> 80 LOAD_CONST 5 (None) 82 RETURN_VALUE |
1 0 LOAD_CONST 0 ((1, 1, 1)) 2 UNPACK_SEQUENCE 3 4 STORE_NAME 0 (a) 6 STORE_NAME 1 (b) 8 STORE_NAME 2 (c) 2 10 SETUP_FINALLY 32 (to 44) 12 SETUP_FINALLY 8 (to 22) 3 14 LOAD_CONST 1 (3) 16 STORE_NAME 0 (a) 18 POP_BLOCK 20 JUMP_FORWARD 14 (to 36) 4 >> 22 POP_TOP 24 POP_TOP 26 POP_TOP 5 28 LOAD_CONST 2 (5) 30 STORE_NAME 1 (b) 32 POP_EXCEPT 34 JUMP_FORWARD 0 (to 36) >> 36 POP_BLOCK 7 38 LOAD_CONST 3 (7) 40 STORE_NAME 2 (c) 42 JUMP_FORWARD 6 (to 50) >> 44 LOAD_CONST 3 (7) 46 STORE_NAME 2 (c) 48 RERAISE 8 >> 50 LOAD_NAME 0 (a) 52 LOAD_CONST 1 (3) 54 COMPARE_OP 2 (==) 56 POP_JUMP_IF_FALSE 74 58 LOAD_NAME 1 (b) 60 LOAD_CONST 4 (1) 62 COMPARE_OP 2 (==) 64 POP_JUMP_IF_FALSE 74 66 LOAD_NAME 2 (c) 68 LOAD_CONST 3 (7) 70 COMPARE_OP 2 (==) 72 POP_JUMP_IF_TRUE 78 >> 74 LOAD_ASSERTION_ERROR 76 RAISE_VARARGS 1 >> 78 LOAD_CONST 5 (None) 80 RETURN_VALUE |
Affected tests:
- ExceptionArcTest.test_except_finally
for i in range(3): for j in range(3): a = i + j assert a == 4
In this code, 3.10 jumps from line 3 to line 1. All other versions jump from 3 to 2, and from 2 to 1.
Affected tests:
- LoopArcTest.test_nested_loop
An if-break used to jump from the if to the break and then to the exit of the loop. Now the break is skipped, and the if jumps to the exit.
def whileelse(seq): while seq: n = seq.pop() if n > 4: break # line 5 else: n = 99 return n # line 8
Python 3.9 dis | Python 3.10 dis |
1 0 LOAD_CONST 0 (<code object whileelse at 0x10da57660, file "while_else.py", line 1>) 2 LOAD_CONST 1 ('whileelse') 4 MAKE_FUNCTION 0 6 STORE_NAME 0 (whileelse) 8 LOAD_CONST 2 (None) 10 RETURN_VALUE Disassembly of <code object whileelse at 0x10da57660, file "while_else.py", line 1>: 2 >> 0 LOAD_FAST 0 (seq) 2 POP_JUMP_IF_FALSE 24 3 4 LOAD_FAST 0 (seq) 6 LOAD_METHOD 0 (pop) 8 CALL_METHOD 0 10 STORE_FAST 1 (n) 4 12 LOAD_FAST 1 (n) 14 LOAD_CONST 1 (4) 16 COMPARE_OP 4 (>) 18 POP_JUMP_IF_FALSE 0 5 20 JUMP_ABSOLUTE 28 22 JUMP_ABSOLUTE 0 7 >> 24 LOAD_CONST 2 (99) 26 STORE_FAST 1 (n) 8 >> 28 LOAD_FAST 1 (n) 30 RETURN_VALUE |
1 0 LOAD_CONST 0 (<code object whileelse at 0x10ade4030, file "while_else.py", line 1>) 2 LOAD_CONST 1 ('whileelse') 4 MAKE_FUNCTION 0 6 STORE_NAME 0 (whileelse) 8 LOAD_CONST 2 (None) 10 RETURN_VALUE Disassembly of <code object whileelse at 0x10ade4030, file "while_else.py", line 1>: 2 >> 0 LOAD_FAST 0 (seq) 2 POP_JUMP_IF_FALSE 24 3 4 LOAD_FAST 0 (seq) 6 LOAD_METHOD 0 (pop) 8 CALL_METHOD 0 10 STORE_FAST 1 (n) 4 12 LOAD_FAST 1 (n) 14 LOAD_CONST 1 (4) 16 COMPARE_OP 4 (>) 18 POP_JUMP_IF_FALSE 0 8 20 LOAD_FAST 1 (n) 22 RETURN_VALUE 7 >> 24 LOAD_CONST 2 (99) 26 STORE_FAST 1 (n) 8 28 LOAD_FAST 1 (n) 30 RETURN_VALUE |
Affected tests:
- LoopArcTest.test_while_else
The last iteration of a for loop used to jump back to the for-statement, which then jumped to after the for loop. Now the last statement in the for loop jumps to the statement after the for loop.
Affected tests:
- LoopArcTest.test_for_if_else_for
When the break happens, this code used to (in Python 3.8 and 3.9) jump from 7 to 10 to 7 to 13. Now (in Python 3.10) it jumps from 7 to 10 to 13.
a, c, d, i = 1, 1, 1, 99 try: for i in range(3): try: a = 5 if i > 0: break # line 7 a = 8 finally: c = 10 except: d = 12 # line 12 assert a == 5 and c == 10 and d == 1 # line 13
Python 3.9 dis | Python 3.10 dis |
1 0 LOAD_CONST 0 ((1, 1, 1, 99)) 2 UNPACK_SEQUENCE 4 4 STORE_NAME 0 (a) 6 STORE_NAME 1 (c) 8 STORE_NAME 2 (d) 10 STORE_NAME 3 (i) 2 12 SETUP_FINALLY 60 (to 74) 3 14 LOAD_NAME 4 (range) 16 LOAD_CONST 1 (3) 18 CALL_FUNCTION 1 20 GET_ITER >> 22 FOR_ITER 46 (to 70) 24 STORE_NAME 3 (i) 4 26 SETUP_FINALLY 34 (to 62) 5 28 LOAD_CONST 2 (5) 30 STORE_NAME 0 (a) 6 32 LOAD_NAME 3 (i) 34 LOAD_CONST 3 (0) 36 COMPARE_OP 4 (>) 38 POP_JUMP_IF_FALSE 50 7 40 POP_BLOCK 10 42 LOAD_CONST 4 (10) 44 STORE_NAME 1 (c) 7 46 POP_TOP 48 JUMP_ABSOLUTE 70 8 >> 50 LOAD_CONST 5 (8) 52 STORE_NAME 0 (a) 54 POP_BLOCK 10 56 LOAD_CONST 4 (10) 58 STORE_NAME 1 (c) 60 JUMP_ABSOLUTE 22 >> 62 LOAD_CONST 4 (10) 64 STORE_NAME 1 (c) 66 RERAISE 68 JUMP_ABSOLUTE 22 >> 70 POP_BLOCK 72 JUMP_FORWARD 16 (to 90) 11 >> 74 POP_TOP 76 POP_TOP 78 POP_TOP 12 80 LOAD_CONST 6 (12) 82 STORE_NAME 2 (d) 84 POP_EXCEPT 86 JUMP_FORWARD 2 (to 90) 88 RERAISE 13 >> 90 LOAD_NAME 0 (a) 92 LOAD_CONST 2 (5) 94 COMPARE_OP 2 (==) 96 POP_JUMP_IF_FALSE 114 98 LOAD_NAME 1 (c) 100 LOAD_CONST 4 (10) 102 COMPARE_OP 2 (==) 104 POP_JUMP_IF_FALSE 114 106 LOAD_NAME 2 (d) 108 LOAD_CONST 7 (1) 110 COMPARE_OP 2 (==) 112 POP_JUMP_IF_TRUE 118 >> 114 LOAD_ASSERTION_ERROR 116 RAISE_VARARGS 1 >> 118 LOAD_CONST 8 (None) 120 RETURN_VALUE |
1 0 LOAD_CONST 0 ((1, 1, 1, 99)) 2 UNPACK_SEQUENCE 4 4 STORE_NAME 0 (a) 6 STORE_NAME 1 (c) 8 STORE_NAME 2 (d) 10 STORE_NAME 3 (i) 2 12 SETUP_FINALLY 58 (to 72) 3 14 LOAD_NAME 4 (range) 16 LOAD_CONST 1 (3) 18 CALL_FUNCTION 1 20 GET_ITER >> 22 FOR_ITER 44 (to 68) 24 STORE_NAME 3 (i) 4 26 SETUP_FINALLY 34 (to 62) 5 28 LOAD_CONST 2 (5) 30 STORE_NAME 0 (a) 6 32 LOAD_NAME 3 (i) 34 LOAD_CONST 3 (0) 36 COMPARE_OP 4 (>) 38 POP_JUMP_IF_FALSE 50 7 40 POP_BLOCK 10 42 LOAD_CONST 4 (10) 44 STORE_NAME 1 (c) 7 46 POP_TOP 48 JUMP_ABSOLUTE 68 8 >> 50 LOAD_CONST 5 (8) 52 STORE_NAME 0 (a) 54 POP_BLOCK 10 56 LOAD_CONST 4 (10) 58 STORE_NAME 1 (c) 60 JUMP_ABSOLUTE 22 >> 62 LOAD_CONST 4 (10) 64 STORE_NAME 1 (c) 66 RERAISE >> 68 POP_BLOCK 70 JUMP_FORWARD 14 (to 86) 11 >> 72 POP_TOP 74 POP_TOP 76 POP_TOP 12 78 LOAD_CONST 6 (12) 80 STORE_NAME 2 (d) 82 POP_EXCEPT 84 JUMP_FORWARD 0 (to 86) 13 >> 86 LOAD_NAME 0 (a) 88 LOAD_CONST 2 (5) 90 COMPARE_OP 2 (==) 92 POP_JUMP_IF_FALSE 110 94 LOAD_NAME 1 (c) 96 LOAD_CONST 4 (10) 98 COMPARE_OP 2 (==) 100 POP_JUMP_IF_FALSE 110 102 LOAD_NAME 2 (d) 104 LOAD_CONST 7 (1) 106 COMPARE_OP 2 (==) 108 POP_JUMP_IF_TRUE 114 >> 110 LOAD_ASSERTION_ERROR 112 RAISE_VARARGS 1 >> 114 LOAD_CONST 8 (None) 116 RETURN_VALUE |
Another example is harder to understand. Here we used to have a jump from 3 to 13, which no longer happens, but we get a new jump from 3 to 10, which I don't understand:
a, b, c, d, i = 1, 1, 1, 1, 99 try: for i in range(5): try: a = 5 if i > 0: continue # line 7 b = 8 finally: c = 10 except: d = 12 # line 12 assert (a, b, c, d) == (5, 8, 10, 1) # line 13
Python 3.9 dis | Python 3.10 dis |
1 0 LOAD_CONST 0 ((1, 1, 1, 1, 99)) 2 UNPACK_SEQUENCE 5 4 STORE_NAME 0 (a) 6 STORE_NAME 1 (b) 8 STORE_NAME 2 (c) 10 STORE_NAME 3 (d) 12 STORE_NAME 4 (i) 2 14 SETUP_FINALLY 58 (to 74) 3 16 LOAD_NAME 5 (range) 18 LOAD_CONST 1 (5) 20 CALL_FUNCTION 1 22 GET_ITER >> 24 FOR_ITER 44 (to 70) 26 STORE_NAME 4 (i) 4 28 SETUP_FINALLY 32 (to 62) 5 30 LOAD_CONST 1 (5) 32 STORE_NAME 0 (a) 6 34 LOAD_NAME 4 (i) 36 LOAD_CONST 2 (0) 38 COMPARE_OP 4 (>) 40 POP_JUMP_IF_FALSE 50 7 42 POP_BLOCK 10 44 LOAD_CONST 3 (10) 46 STORE_NAME 2 (c) 7 48 JUMP_ABSOLUTE 24 8 >> 50 LOAD_CONST 4 (8) 52 STORE_NAME 1 (b) 54 POP_BLOCK 10 56 LOAD_CONST 3 (10) 58 STORE_NAME 2 (c) 60 JUMP_ABSOLUTE 24 >> 62 LOAD_CONST 3 (10) 64 STORE_NAME 2 (c) 66 RERAISE 68 JUMP_ABSOLUTE 24 >> 70 POP_BLOCK 72 JUMP_FORWARD 16 (to 90) 11 >> 74 POP_TOP 76 POP_TOP 78 POP_TOP 12 80 LOAD_CONST 5 (12) 82 STORE_NAME 3 (d) 84 POP_EXCEPT 86 JUMP_FORWARD 2 (to 90) 88 RERAISE 13 >> 90 LOAD_NAME 0 (a) 92 LOAD_NAME 1 (b) 94 LOAD_NAME 2 (c) 96 LOAD_NAME 3 (d) 98 BUILD_TUPLE 4 100 LOAD_CONST 6 ((5, 8, 10, 1)) 102 COMPARE_OP 2 (==) 104 POP_JUMP_IF_TRUE 110 106 LOAD_ASSERTION_ERROR 108 RAISE_VARARGS 1 >> 110 LOAD_CONST 7 (None) 112 RETURN_VALUE |
1 0 LOAD_CONST 0 ((1, 1, 1, 1, 99)) 2 UNPACK_SEQUENCE 5 4 STORE_NAME 0 (a) 6 STORE_NAME 1 (b) 8 STORE_NAME 2 (c) 10 STORE_NAME 3 (d) 12 STORE_NAME 4 (i) 2 14 SETUP_FINALLY 56 (to 72) 3 16 LOAD_NAME 5 (range) 18 LOAD_CONST 1 (5) 20 CALL_FUNCTION 1 22 GET_ITER >> 24 FOR_ITER 42 (to 68) 26 STORE_NAME 4 (i) 4 28 SETUP_FINALLY 32 (to 62) 5 30 LOAD_CONST 1 (5) 32 STORE_NAME 0 (a) 6 34 LOAD_NAME 4 (i) 36 LOAD_CONST 2 (0) 38 COMPARE_OP 4 (>) 40 POP_JUMP_IF_FALSE 50 7 42 POP_BLOCK 10 44 LOAD_CONST 3 (10) 46 STORE_NAME 2 (c) 7 48 JUMP_ABSOLUTE 24 8 >> 50 LOAD_CONST 4 (8) 52 STORE_NAME 1 (b) 54 POP_BLOCK 10 56 LOAD_CONST 3 (10) 58 STORE_NAME 2 (c) 60 JUMP_ABSOLUTE 24 >> 62 LOAD_CONST 3 (10) 64 STORE_NAME 2 (c) 66 RERAISE >> 68 POP_BLOCK 70 JUMP_FORWARD 14 (to 86) 11 >> 72 POP_TOP 74 POP_TOP 76 POP_TOP 12 78 LOAD_CONST 5 (12) 80 STORE_NAME 3 (d) 82 POP_EXCEPT 84 JUMP_FORWARD 0 (to 86) 13 >> 86 LOAD_NAME 0 (a) 88 LOAD_NAME 1 (b) 90 LOAD_NAME 2 (c) 92 LOAD_NAME 3 (d) 94 BUILD_TUPLE 4 96 LOAD_CONST 6 ((5, 8, 10, 1)) 98 COMPARE_OP 2 (==) 100 POP_JUMP_IF_TRUE 106 102 LOAD_ASSERTION_ERROR 104 RAISE_VARARGS 1 >> 106 LOAD_CONST 7 (None) 108 RETURN_VALUE |
Affected tests:
- ExceptionArcTest.test_break_through_finally
- ExceptionArcTest.test_continue_through_finally