Working with File Input and Output
The guardian pattern adds protective checks before accessing data, preventing IndexError and other crashes when input is incomplete or malformed.
The Risk in Line Parsing
When a program parses a file line by line, it may split each line into a list of words and then access particular list elements. That access is only safe when the required element exists. An empty line produces an empty list, so trying to access words[0] causes an IndexError before the program can perform a comparison with the first word.
What do you think happens?
What happens when a blank line is split into words and the program immediately evaluates words[0]?
Reveal answer
The program raises an IndexError.
A blank or whitespace-only line produces an empty list. Because that list has no element at index 0, words[0] cannot be accessed.
The First Guardian Check
A guardian pattern places a protective check before a risky operation. For file parsing, the first guardian checks whether the list of words is empty. If len(words) == 0 is true, continue immediately moves to the next loop iteration. The program therefore never reaches words[0] for an empty list.
Filtering with Continue
for line in file:
words = line.split()
if len(words) == 0:
continue
if words[0] != 'From':
continue
print(words[2])- 1The first guard handles incomplete input with no words.
- 2The first continue skips the rest of the current iteration when the list is empty.
- 3After the first guard passes, words[0] can be checked.
- 4The second guard filters out lines that do not start with 'From'.
- 5Only lines passing both guards reach the processing statement.
- Each loop iteration handles a line from the file.
- The line is split into a list of words.
- An empty list is rejected before words[0] is accessed.
- Lines whose first word is not 'From' are rejected.
- The remaining line is processed.
Progressive Data Refinement
The guards refine the input in stages. The first continue removes lines with no words. The second continue removes lines whose first word is not 'From'. A line that reaches the processing statement has passed both tests: it contains at least one word, and its first word is 'From'. Each guard narrows the set of data that the remaining code must handle.
Why One Guard Is Not Enough
Passing the first guard does not prove that every later list access is safe. The check len(words) == 0 only establishes that the list has at least one element. If a line starts with 'From' but contains only one or two words, print(words[2]) can still raise an IndexError because the third element does not exist.
Flat Guards and Nested Conditions
Guardian clauses keep the loop body flat. Each guard appears near the top and says that a line failing one condition should be skipped. The main processing logic remains at one indentation level instead of being placed inside several nested if statements. Deep nesting can create the Pyramid of Doom, which becomes harder to read and maintain.
The two initial guards can also be combined with the or operator. This single condition means that the loop skips a line when it has no words or when its first word is not 'From'. Combining conditions can be concise while remaining readable when the complete skip rule is clear.
A Guard Review Exercise
Finding the Remaining Unsafe Access
A parser skips empty lines and lines whose first word is not 'From', then executes print(words[2]). Which access still needs protection?
- 1Inspect the first guard
The empty-list guard prevents access to words[0] when there are no words.
- 2Inspect the second guard
After the first guard passes, the program can check words[0] and skip lines that do not start with 'From'.
- 3Inspect the processing statement
The statement
print(words[2])requires a third list element. A line may start with 'From' and still have fewer than three words. - 4Add the missing safety requirement
The parser needs an additional guard that ensures enough words exist before accessing words[2].
The access to words[2] is the remaining unsafe operation. The existing guards do not establish that the list contains a third element.
Construct a test file containing a line that passes the empty-line check and the 'From' check but still causes the final words[2] access to fail. Then identify the additional condition the parser must verify.
Hints
- The line must contain at least one word so that words[0] exists.
- Its first word must be 'From'.
- It must still have too few words for index 2.
Common Guardian Mistakes
Common mistake Accessing words[0] before checking whether the list is empty.
What it looks likeThe comparison with 'From' is attempted immediately after splitting the line.
Why it failsAn empty line produces an empty list, so words[0] raises
IndexError.Do this insteadCheck len(words) == 0 first and use continue when the list is empty.
Common mistake Assuming that a nonempty list is long enough for every later access.
What it looks likeThe code guards words[0] and then assumes words[2] must exist.
Why it failsA list can contain one or two words while still having a valid element at index 0.
Do this insteadAdd another guard for the list length required by the later access.
Common mistake Using a guard but forgetting continue.
What it looks likeThe program detects an unwanted line but continues executing the rest of the loop body.
Why it failsThe guard has not actually prevented later processing for that iteration.
Do this insteadUse continue so the current iteration ends immediately and the next line is considered.
Common mistake Replacing clear guards with deeply nested if statements.
What it looks likeEvery later operation is indented inside another condition.
Why it failsDeep nesting creates the Pyramid of Doom and makes the processing logic harder to read and maintain.
Do this insteadPlace guard clauses near the top of the loop and keep the main processing path flat.
Practice Check
Explain the sequence of decisions for one input line: first determine what happens when the split result is empty, then determine what happens when the first word is not 'From', and finally identify what must be true before words[2] is accessed.
Hints
- Each continue ends the current iteration.
- The first guard protects words[0].
- The later access needs a separate length guarantee.
Key Takeaways
- 1Place a guardian check before any list access that could fail.
- 2Use continue to skip empty, malformed, or uninteresting input and move to the next loop iteration.
- 3Treat multiple guards as a refinement process: each one narrows the input that reaches the processing logic.
- 4A guard for words[0] does not automatically protect words[2]; inspect every later indexed access.
- 5Flat guard clauses are an alternative to deeply nested if statements and help avoid the Pyramid of Doom.
At a glance
- Guardian patterns put protective checks before risky list access.
- The continue statement skips the rest of the current iteration when input is empty or uninteresting.
- Multiple guards progressively refine file input before processing.
- Every indexed access needs a suitable safety check, including accesses made after earlier guards pass.
- Flat guard clauses keep parsing logic more readable than deeply nested conditions.