Exceptions and Narrow try/except Blocks
Unit ID: M06-U04 Estimated active time: 25-35 minutes
Catch an expected failure at the right level
When processing several independent records, one invalid record should not necessarily stop the whole review.
accepted = []
rejected = []
for position, record in enumerate(records, start=1):
try:
hours = parse_hours(record["hours"])
except (KeyError, TypeError, ValueError) as error:
rejected.append(
{"position": position, "reason": str(error)}
)
else:
accepted.append({"id": record["id"], "hours": hours})
The try block contains only the operations expected to fail for invalid input. else runs when no exception occurs.
Avoid bare except
Do not write:
except:
pass
It hides unexpected failures and discards evidence. Catch named exception types that the code is prepared to handle.
Keep context
Record position, safe record ID, exception type, and message when useful. Do not include sensitive raw data in logs or learner reports.
except (TypeError, ValueError) as error:
rejected.append(
{
"position": position,
"error_type": type(error).__name__,
"reason": str(error),
}
)
Do not catch your own programming defects too broadly
An unexpected NameError or AttributeError may reveal a coding defect, not invalid learner data. Let unexpected exceptions surface during development.
finally
A finally block runs whether or not an exception occurs. It is useful for required cleanup, such as closing a resource not already managed by a context manager. It is not required for this checkpoint.
Practice
Process [{"hours": "8"}, {}, {"hours": "six"}, {"hours": 12}]. Keep accepted hours and rejected error details. Confirm that the valid final record still runs after earlier failures.
Takeaway
Catch only expected exceptions, keep the protected block narrow, and preserve context. Do not silence unexpected defects. Next, we will raise errors whose types and messages explain a broken contract.
