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.
Code after break/continue is no longer compiled
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
First line number of modules
# 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
Except clause when no exception
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