Loop else blocks
Python loops have an optional else block that executes if the loop terminates naturally (without a return/break).
This example is taken from The Sims 4 (simulation/notebook/notebook_tracker.pyc).
0 LOAD_CONST 0 (None)
2 STORE_FAST 3 (entry)
4 SETUP_LOOP 60 (to 66)
6 LOAD_FAST 0 (self)
8 LOAD_ATTR 0 (_notebook_entries)
10 LOAD_FAST 1 (subcategory_id)
12 BINARY_SUBSCR
14 GET_ITER
>> 16 FOR_ITER 20 (to 38)
18 STORE_FAST 4 (notebook_entry)
20 LOAD_FAST 2 (entry_id)
22 LOAD_FAST 4 (notebook_entry)
24 LOAD_ATTR 1 (entry_object_definition_id)
26 COMPARE_OP 2 (==)
28 POP_JUMP_IF_FALSE 16
30 LOAD_FAST 4 (notebook_entry)
32 STORE_FAST 3 (entry)
34 BREAK_LOOP
36 JUMP_ABSOLUTE 16
>> 38 POP_BLOCK
40 LOAD_GLOBAL 2 (logger)
42 LOAD_METHOD 3 (error)
44 LOAD_CONST 1 ('Failed to find notebook entry with SubcategoryId: {} and EntryDefinitionId: {}
on Sim: {}.')
46 LOAD_GLOBAL 4 (NotebookSubCategories)
48 LOAD_FAST 1 (subcategory_id)
50 CALL_FUNCTION 1
52 LOAD_FAST 2 (entry_id)
54 LOAD_FAST 0 (self)
56 LOAD_ATTR 5 (_owner)
58 CALL_METHOD 4
60 POP_TOP
62 LOAD_CONST 0 (None)
64 RETURN_VALUE
>> 66 SETUP_LOOP 46 (to 114)
68 LOAD_GLOBAL 6 (enumerate)
70 LOAD_FAST 3 (entry)
72 LOAD_ATTR 7 (sub_entries)
74 CALL_FUNCTION 1
76 GET_ITER
>> 78 FOR_ITER 32 (to 112)
80 UNPACK_SEQUENCE 2
82 STORE_FAST 5 (i)
84 STORE_FAST 6 (sub_entry)
86 LOAD_FAST 6 (sub_entry)
88 LOAD_ATTR 8 (new_sub_entry)
90 POP_JUMP_IF_FALSE 78
92 LOAD_GLOBAL 9 (SubEntryData)
94 LOAD_FAST 6 (sub_entry)
96 LOAD_ATTR 10 (sub_entry_id)
98 LOAD_CONST 2 (False)
100 CALL_FUNCTION 2
102 LOAD_FAST 3 (entry)
104 LOAD_ATTR 7 (sub_entries)
106 LOAD_FAST 5 (i)
108 STORE_SUBSCR
110 JUMP_ABSOLUTE 78
>> 112 POP_BLOCK
>> 114 LOAD_CONST 0 (None)
116 RETURN_VALUE
The SETUP_LOOP jump target is after the entire loop body. So, for this example, op 66 must occur after a DEDENT. The ops after op 38 POP_BLOCK and before op 66 are the body of the else block.
This is the result from py37dec:
def mark_entry_as_seen(self, subcategory_id, entry_id):
entry = None
for notebook_entry in self._notebook_entries[subcategory_id]:
if entry_id == notebook_entry.entry_object_definition_id:
entry = notebook_entry
break
logger.error('Failed to find notebook entry with SubcategoryId: {} and EntryDefinitionId: {} on Sim: {}.', NotebookSubCategories(subcategory_id), entry_id, self._owner)
return
for (i, sub_entry) in enumerate(entry.sub_entries):
if sub_entry.new_sub_entry:
entry.sub_entries[i] = SubEntryData(sub_entry.sub_entry_id, False)
Below are the operations for the py37dec generated code:
0 LOAD_CONST 0 (None)
2 STORE_FAST 3 (entry)
4 SETUP_LOOP 34 (to 40)
6 LOAD_FAST 0 (self)
8 LOAD_ATTR 0 (_notebook_entries)
10 LOAD_FAST 1 (subcategory_id)
12 BINARY_SUBSCR
14 GET_ITER
>> 16 FOR_ITER 20 (to 38)
18 STORE_FAST 4 (notebook_entry)
20 LOAD_FAST 2 (entry_id)
22 LOAD_FAST 4 (notebook_entry)
24 LOAD_ATTR 1 (entry_object_definition_id)
26 COMPARE_OP 2 (==)
28 POP_JUMP_IF_FALSE 16
30 LOAD_FAST 4 (notebook_entry)
32 STORE_FAST 3 (entry)
34 BREAK_LOOP
36 JUMP_ABSOLUTE 16
>> 38 POP_BLOCK
>> 40 LOAD_GLOBAL 2 (logger)
42 LOAD_METHOD 3 (error)
44 LOAD_CONST 1 ('Failed to find notebook entry with SubcategoryId: {} and EntryDefinitionId: {} on Sim: {}.')
46 LOAD_GLOBAL 4 (NotebookSubCategories)
48 LOAD_FAST 1 (subcategory_id)
50 CALL_FUNCTION 1
52 LOAD_FAST 2 (entry_id)
54 LOAD_FAST 0 (self)
56 LOAD_ATTR 5 (_owner)
58 CALL_METHOD 4
60 POP_TOP
62 LOAD_CONST 0 (None)
64 RETURN_VALUE
>> 66 FOR_ITER 32 (to 100)
68 UNPACK_SEQUENCE 2
70 STORE_FAST 5 (i)
72 STORE_FAST 6 (sub_entry)
74 LOAD_FAST 6 (sub_entry)
76 LOAD_ATTR 8 (new_sub_entry)
78 POP_JUMP_IF_FALSE 66
80 LOAD_GLOBAL 9 (SubEntryData)
82 LOAD_FAST 6 (sub_entry)
84 LOAD_ATTR 10 (sub_entry_id)
86 LOAD_CONST 2 (False)
88 CALL_FUNCTION 2
90 LOAD_FAST 3 (entry)
92 LOAD_ATTR 7 (sub_entries)
94 LOAD_FAST 5 (i)
96 STORE_SUBSCR
98 JUMP_ABSOLUTE 66
>> 100 POP_BLOCK
102 LOAD_CONST 0 (None)
104 RETURN_VALUE
This is my hand-translated expectation:
def mark_entry_as_seen(self, subcategory_id, entry_id):
entry = None
for notebook_entry in self._notebook_entries[subcategory_id]:
if entry_id == notebook_entry.entry_object_definition_id:
entry = notebook_entry
break
else:
logger.error(
"Failed to find notebook entry with SubcategoryId: {} and EntryDefinitionId: {} on Sim: {}.",
NotebookSubCategories(subcategory_id),
entry_id,
self._owner,
)
return
for i, sub_entry in enumerate(entry.sub_entries):
if sub_entry.new_sub_entry:
entry.sub_entries[i] = SubEntryData(sub_entry.sub_entry_id, False)
Below are the operations from my translation
0 LOAD_CONST 0 (None)
2 STORE_FAST 3 (entry)
4 SETUP_LOOP 60 (to 66)
6 LOAD_FAST 0 (self)
8 LOAD_ATTR 0 (_notebook_entries)
10 LOAD_FAST 1 (subcategory_id)
12 BINARY_SUBSCR
14 GET_ITER
>> 16 FOR_ITER 20 (to 38)
18 STORE_FAST 4 (notebook_entry)
20 LOAD_FAST 2 (entry_id)
22 LOAD_FAST 4 (notebook_entry)
24 LOAD_ATTR 1 (entry_object_definition_id)
26 COMPARE_OP 2 (==)
28 POP_JUMP_IF_FALSE 16
30 LOAD_FAST 4 (notebook_entry)
32 STORE_FAST 3 (entry)
34 BREAK_LOOP
36 JUMP_ABSOLUTE 16
>> 38 POP_BLOCK
40 LOAD_GLOBAL 2 (logger)
42 LOAD_METHOD 3 (error)
44 LOAD_CONST 1 ('Failed to find notebook entry with SubcategoryId: {} and EntryDefinitionId: {} on Sim: {}.')
46 LOAD_GLOBAL 4 (NotebookSubCategories)
48 LOAD_FAST 1 (subcategory_id)
50 CALL_FUNCTION 1
52 LOAD_FAST 2 (entry_id)
54 LOAD_FAST 0 (self)
56 LOAD_ATTR 5 (_owner)
58 CALL_METHOD 4
60 POP_TOP
62 LOAD_CONST 0 (None)
64 RETURN_VALUE
>> 66 SETUP_LOOP 46 (to 114)
68 LOAD_GLOBAL 6 (enumerate)
70 LOAD_FAST 3 (entry)
72 LOAD_ATTR 7 (sub_entries)
74 CALL_FUNCTION 1
76 GET_ITER
>> 78 FOR_ITER 32 (to 112)
80 UNPACK_SEQUENCE 2
82 STORE_FAST 5 (i)
84 STORE_FAST 6 (sub_entry)
86 LOAD_FAST 6 (sub_entry)
88 LOAD_ATTR 8 (new_sub_entry)
90 POP_JUMP_IF_FALSE 78
92 LOAD_GLOBAL 9 (SubEntryData)
94 LOAD_FAST 6 (sub_entry)
96 LOAD_ATTR 10 (sub_entry_id)
98 LOAD_CONST 2 (False)
100 CALL_FUNCTION 2
102 LOAD_FAST 3 (entry)
104 LOAD_ATTR 7 (sub_entries)
106 LOAD_FAST 5 (i)
108 STORE_SUBSCR
110 JUMP_ABSOLUTE 78
>> 112 POP_BLOCK
>> 114 LOAD_CONST 0 (None)
116 RETURN_VALUE