Python course module guide: Exception management
Python custom exceptions give domain failures meaningful names. A clear exception boundary helps callers distinguish invalid input, missing records and unavailable services without parsing vague message text.
What you will learn
- Create a small domain exception hierarchy.
- Raise errors at the point of knowledge.
- Preserve causes with
raise ... from. - Catch failures only where recovery is possible.
Why create a custom exception?
Built-in exceptions such as ValueError and KeyError are often sufficient inside small functions. A custom exception becomes useful when the failure belongs to the application’s domain and callers need to react differently. Names such as InvalidEnrollment or PaymentDeclined communicate intent better than a generic error string.
class EnrollmentError(Exception):
"""Base error for enrolment operations."""
class InvalidCourseCode(EnrollmentError):
pass
class SeatUnavailable(EnrollmentError):
pass
Keep the hierarchy small. A shared base class lets an application catch every enrolment failure when appropriate while still allowing specific handling.
Raise at the point of knowledge
def reserve_seat(course_code: str, available_seats: int) -> str:
normalized = course_code.strip().upper()
if not normalized.startswith("PY-"):
raise InvalidCourseCode(f"Unsupported course code: {normalized}")
if available_seats <= 0:
raise SeatUnavailable(f"No seats available for {normalized}")
return f"Seat reserved for {normalized}"
The function that knows the rule should raise the error. A user interface, command handler or API route can decide how to present it. Avoid returning a mixture of strings, None and special numbers to represent failures.
Catch only where recovery is possible
try:
message = reserve_seat(user_code, seats)
except InvalidCourseCode as error:
print(f"Check the course code. {error}")
except SeatUnavailable:
print("Choose another batch or join the waiting list.")
else:
print(message)
Each handler provides a specific next action. A broad except Exception at every layer usually hides programming errors and creates misleading success states. The Python debugging guide explains how to read tracebacks instead of suppressing them.
Preserve the original cause
class ConfigurationError(Exception):
pass
def read_port(value: str) -> int:
try:
port = int(value)
except ValueError as error:
raise ConfigurationError("PORT must be an integer") from error
if not 1 <= port <= 65535:
raise ConfigurationError("PORT is outside the valid range")
return port
raise ... from error adds domain context while preserving the underlying cause. This is valuable when logs need both the user-facing meaning and the technical origin.
Add useful attributes
class ValidationError(Exception):
def __init__(self, field: str, message: str):
self.field = field
self.message = message
super().__init__(f"{field}: {message}")
raise ValidationError("phone", "must contain 10 digits")
Structured attributes are easier to map to a form or JSON response than parsing text. Do not put passwords, tokens or sensitive personal data into exception messages because logs may store them.
Map exceptions at application boundaries
A CLI can translate a domain error into a clear message and non-zero exit code. A web API can translate it into a suitable HTTP status and JSON error object. Keep HTTP concepts out of the core domain function when that function may also be used by a script or background job. The Flask REST API with SQLite demonstrates this separation.
Test the failure contract
import pytest
def test_rejects_unknown_course_code():
with pytest.raises(InvalidCourseCode, match="Unsupported course code"):
reserve_seat("java-01", 3)
def test_rejects_full_batch():
with pytest.raises(SeatUnavailable):
reserve_seat("PY-01", 0)
Tests document which failures callers can depend on. Continue with the pytest guide for fixtures, parametrization and monkeypatching.
File and data errors
Do not replace every FileNotFoundError or CSV parsing error with one vague custom exception. Add domain context only when it helps the caller act. The Python file handling guide covers the built-in failures you should understand first.
Common mistakes
- Creating a separate exception class for every sentence.
- Catching an error and returning success anyway.
- Losing the original traceback when translating exceptions.
- Using exception messages as program logic instead of exception types or attributes.
- Exposing secrets or internal SQL details to users.
- Using exceptions for ordinary expected branching that a return value could express clearly.
Practice exercise
Design exceptions for a small library system: invalid member, unknown book and unavailable copy. Raise them in domain functions, translate them into CLI messages at the outer boundary and write one test per failure. Add error chaining when converting a malformed numeric identifier.
Custom exception FAQs
Should custom exceptions inherit from Exception?
Application exceptions normally inherit directly or indirectly from Exception, not BaseException, so ordinary handlers behave as expected.
When is ValueError enough?
Use it when the caller only needs to know that a value is invalid. Create a domain type when callers need a stable, meaningful category for recovery.
Should error messages be shown directly to users?
Not always. Translate internal errors at the UI or API boundary, especially when messages could reveal implementation or sensitive information.
Official reference: Python tutorial on user-defined exceptions.
Learn Python with guided practice in Visakhapatnam
These concepts become useful when you apply them in exercises, assignments and reviewed projects. Explore Python training in Vizag for the complete curriculum, classroom and online learning options, and current batch details.
Python module learning path
Continue through the related practical guides in this course-module series: