"I’ve got a bug that’s baffling me, Margaret," Timothy said, frustrated. "I’m trying to catch a ConnectionError when I fetch data. But for some reason, my except block is catching errors from my process() function too! I only wanted to guard the network call."
Margaret looked at his code. "That’s because you’ve trapped both the network call and the processing logic in the same block, expanding your error-handling scope further than you intended."
The Problem: Oversized Error Scope
Margaret pasted Timothy’s code into the Python Structure Viewer to show him the logical grouping.
Timothy’s Code:
try:
data = fetch_data()
process(data)
except ConnectionError:
print("Failed: Connection Error")
Python Structure Viewer output:
=== TREE VIEW ...
"I’ve got a bug that’s baffling me, Margaret," Timothy said, frustrated. "I’m trying to catch a ConnectionError when I fetch data. But for some reason, my except block is catching errors from my process() function too! I only wanted to guard the network call."
Margaret looked at his code. "That’s because you’ve trapped both the network call and the processing logic in the same block, expanding your error-handling scope further than you intended."
The Problem: Oversized Error Scope
Margaret pasted Timothy’s code into the Python Structure Viewer to show him the logical grouping.
Timothy’s Code:
try:
data = fetch_data()
process(data)
except ConnectionError:
print("Failed: Connection Error")
Python Structure Viewer output:
=== TREE VIEW ===
Try
data = fetch_data()
process(data)
Except ConnectionError
print('Failed: Connection Error')
=== ENGLISH VIEW ===
Try:
Set data to fetch_data().
Evaluate process(data).
Except ConnectionError:
Evaluate print('Failed: Connection Error').
"Look at the Try block," Margaret noted. "Both fetch_data() and process(data) are nested inside it. If process() fails for a completely different reason—like a math error or a missing key—Python will still trigger your ConnectionError handler. You’ve blurred the line between the risky operation and the subsequent logic."
The Solution: The else Block
"Python has a specific structural anchor for this," Margaret said. "It’s called the else block. It only runs if the try block finishes successfully, without raising an exception."
The Structured Python Snippet:
try:
data = fetch_data()
except ConnectionError:
print("Failed: Connection Error")
else:
process(data)
Python Structure Viewer output:
=== TREE VIEW ===
Try
data = fetch_data()
Except ConnectionError
print('Failed: Connection Error')
Else
process(data)
=== ENGLISH VIEW ===
Try:
Set data to fetch_data().
Except ConnectionError:
Evaluate print('Failed: Connection Error').
Else:
Evaluate process(data).
Timothy stared at the new Tree View. "The Else sits at the same level as the Except. They are parallel branches."
"Exactly," Margaret said. "Structurally, you have isolated the network call. The Except branch handles the failure, and the Else branch handles the success."
Isolating the Logic
"By moving process(data) into the else block," Margaret continued, "you ensure that your error handling is surgically precise. If process(data) fails now, it won’t be caught by the connection handler. The error will bubble up normally, alerting you to a real bug in your processing logic rather than masking it as a network failure."
Timothy nodded. "In the first version, the processing was happening inside the danger zone. In the second version, I only start processing once the risky part is over."
"That’s the power of the else clause," Margaret concluded. "It keeps your try block focused only on the code that might raise the specific exception you’re looking for. It’s a pattern you should use whenever you want to perform additional work only after a specific risky operation succeeds."
Analyze Python structure yourself
Download the Python Structure Viewer — a free tool that shows code structure in tree and plain English views. Works offline, no installation required.
Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.