Skip to content

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
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information