Error Handling with Try-Except
Defensive programming means asking 'What could go wrong?' before your program reaches users.
The Empty-Line Failure
Imagine a program that reads lines from a user. It skips lines beginning with a hash symbol, stops when the user types done, and prints every other line. The program works for ordinary input, but it crashes when the user presses Enter without typing anything. That empty line exposes a gap in the program's defensive thinking.
Defensive programming means asking what could go wrong before users encounter the program. Empty input is an edge case: it falls outside the expected happy path and must be handled deliberately.
IndexError: string index out of rangeTracing the Missing Character
The expression line[0] asks Python to retrieve the character at index 0. This is valid only when line contains at least one character. An empty string contains zero characters, so there is no character at index 0. Python therefore raises IndexError with the message string index out of range.
The important point is the order of operations. The program attempts the indexing operation before it has checked whether the string contains a character. The solution is to make the safety check happen first, or to use an operation that performs the prefix check safely.
What do you think happens?
What happens when line is an empty string and the program evaluates line.startswith('#')?
Reveal answer
Answer: It returns False
startswith() safely checks whether a string begins with a given substring. For an empty string, it returns False without trying to retrieve a missing character.
Prefix Checks with startswith
The clearest fix for checking whether a line begins with a hash is to replace direct indexing with startswith(). The method expresses the intended question directly: does this string start with a hash? It returns False for an empty string instead of raising IndexError.
| Input line | startswith('#') | Meaning |
|---|---|---|
| empty string | False | There is no first character, but no error occurs |
| hello | False | The line does not begin with a hash |
| #comment | True | The line begins with a hash |
Safe prefix results for the three cases described in the source material
Prefer startswith() for this specific task. It is shorter, clearer, and designed to test a string prefix safely. Use direct indexing only when you genuinely need to access the character itself and have protected that access.
The Guardian Pattern
The guardian pattern places a safety condition before an operation that could fail. For this problem, the guard checks that the line has at least one character before the program evaluates line[0].
Python evaluates an and expression from left to right and stops as soon as its result is determined. If len(line) > 0 is False, the complete and expression cannot be True. Python therefore does not evaluate line[0] == '#'. Because the unsafe lookup is skipped, the empty string cannot cause this IndexError.
Following the Guardian
Determine what the guardian expression does when line is empty and when line is '#comment'.
Empty line: The length check len(line) > 0 is False. Python stops evaluating the and expression, so line[0] is never accessed.
Comment line: The length check is True, so Python safely evaluates line[0] == '#'. The first character is a hash, so the complete condition is True.
The guardian prevents the index lookup for an empty line and permits it for a nonempty line.
Choosing the Safer Technique
| Technique | How it prevents the failure | Best fit |
|---|---|---|
| startswith() | Checks the prefix without directly retrieving a missing character | Checking whether a string begins with a particular substring |
| Guardian pattern | Checks the string length before evaluating the index lookup | Cases where later logic needs indexed access |
For the hash-prefix problem, startswith() is usually the better choice because it says exactly what the program wants to know. The guardian pattern remains important because it demonstrates a general defensive technique: validate a prerequisite before performing a dependent operation.
Mistakes to Avoid
Accessing line[0] before checking whether the line contains a character
An empty string has no index 0, so Python raises IndexError.
Fix:
Use line.startswith('#') or place len(line) > 0 before the index test in an and expression.Assuming normal test input represents every possible user action
A user can press Enter without typing anything, producing an empty line.
Fix:
Ask what unexpected or edge-case input could reach each operation before the program is shipped.Putting the unsafe index check first in the guardian expression
Python evaluates from left to right, so line[0] is attempted before the length guard.
Fix:
Put len(line) > 0 first so a false guard prevents the later lookup.Using the guardian pattern when a direct prefix method communicates the intent more clearly
Although safe, this is more indirect for a simple prefix question.
Fix:
Prefer startswith('#') when the goal is only to test whether the line begins with a hash.
Practice Check
A program checks whether an input string begins with the letter A. Explain which technique you would choose if you only need to test the prefix, and explain how the guardian pattern would protect a direct index lookup.
Hints
- For a prefix-only question, choose the method designed to check a prefix safely.
- For direct indexing, check that the string has at least one character first.
- Remember that and evaluates from left to right and can skip its right side.
What do you think happens?
Will this condition attempt to evaluate line[0] when line is empty: len(line) > 0 and line[0] == '#'
Reveal answer
Answer: No. The length comparison is False, so Python short-circuits the and expression and does not evaluate line[0] == '#'.
The guard comes first. Since the result is already known to be False, evaluating the second condition is unnecessary and would risk the IndexError.
Defensive Programming Takeaways
- Defensive programming starts by asking what could go wrong before users run the program. Directly accessing index 0 fails for an empty string because no character exists there. Use startswith() when you need a safe prefix check. Use the guardian pattern when a length check must protect a later index operation. Place the guard first because Python's left-to-right short-circuit evaluation can prevent the unsafe expression from running.
Key Takeaways
- Empty strings do not have a character at index 0, so direct access can raise IndexError.
- startswith() safely checks a string prefix and returns False for an empty string.
- The guardian pattern checks a prerequisite such as len(line) > 0 before accessing an index.
- Short-circuit evaluation works only when the safe guard appears before the potentially failing condition.
- Anticipating edge cases makes programs more robust, user-friendly, and easier to debug.