7.F Fail: Error Control - JulTob/Python GitHub Wiki
Raising Exceptions in Python π¨
One of the most powerful tools you have as a Python developer is the ability to stop execution when something goes wrong β intentionally.
Instead of letting bugs propagate silently, you can explicitly signal that a condition is invalid. This is done by raising an exception.
π° Core Idea
An exception is a runtime signal that something unexpected or invalid has occurred.
Python allows you to trigger this signal yourself using the raise keyword.
Think of it as:
βThis situation should not happen β abort and report.β
βοΈ Basic Syntax
raise Exception("Error message")
You specify:
- The type of exception
- The explanatory message
π§ͺ Example 1 β Guarding Against Invalid Values
Suppose negative numbers are not allowed:
x = -1
if x < 0:
raise Exception("Sorry, no numbers below zero")
What happens:
- Python evaluates the condition
x < 0 - The condition is
True - The program stops immediately
- The message is displayed
This pattern is called a guard clause β a defensive programming technique.
β οΈ Why Raise Exceptions?
Without exceptions:
- Errors may propagate silently
- Debugging becomes difficult
- Incorrect data contaminates later computations
With exceptions:
- Failures occur early
- Causes are easier to identify
- Code becomes safer and more predictable
This aligns with a key engineering principle:
Fail fast, fail loudly.
π― Choosing the Right Exception Type
Python provides many built-in exception classes.
You should use the most appropriate one to communicate intent.
Example: enforcing integer input.
x = "hello"
if not type(x) is int:
raise TypeError("Only integers are allowed")
Here we use:
TypeErrorβ wrong data type
This is more informative than a generic Exception.
π§ Conceptual Model
You can think of raise as creating an error signal in the program flow:
Normal execution β Condition detected β raise β Program interrupted
This mechanism integrates with:
try / exceptblocks- Error propagation across functions
- Debugging tools
π‘ Best Practices
Prefer specific exceptions
Good:
raise ValueError("Age must be positive")
Less good:
raise Exception("Something went wrong")
Specific errors improve readability and maintainability.
Validate early
Check inputs at the boundaries of your functions.
def sqrt(x):
if x < 0:
raise ValueError("Cannot compute square root of negative number")
Write meaningful messages
Error messages should explain:
- What went wrong
- Why it is invalid
πͺ Advanced Insight β Exceptions as Contracts
Exceptions are closely related to design by contract.
A function implicitly states:
βIf you give me valid input, I guarantee correct output.
Otherwise, I will raise an exception.β
This turns runtime checks into formal guarantees.
β Summary
raiseallows you to trigger exceptions manually- Exceptions stop execution and report errors
- Use specific exception types when possible
- Raising exceptions is essential for robust, safe programs
π§© Creating Custom Exceptions (Your Own Error Types)
Built-in exceptions are useful, but sometimes your program has domain-specific rules that deserve their own error language.
For example:
- A banking app β
InsufficientFundsError - A game engine β
InvalidMoveError - A scientific simulation β
ConvergenceError
When you define custom exceptions, you are giving your code a vocabulary for failure.
This dramatically improves:
- Readability
- Debugging clarity
- Architectural design
π° Core Idea
A custom exception is simply a class that inherits from Exception (or another exception type).
class MyError(Exception):
pass
Thatβs it.
You now have a brand-new error type.
βοΈ Basic Example
Suppose we want to forbid negative ages.
class NegativeAgeError(Exception):
pass
age = -5
if age < 0:
raise NegativeAgeError("Age cannot be negative")
Now the error communicates meaning, not just failure.
π― Why Custom Errors Matter
Compare these two messages:
Generic:
ValueError: Invalid input
Specific:
NegativeAgeError: Age cannot be negative
The second one tells you immediately:
- What failed
- Why it failed
- Where to look
This reduces debugging time dramatically.
π§ Conceptual Model β Errors as Types
In strongly-typed thinking (similar to engineering disciplines or Ada-style design), exceptions are part of your type system of behavior.
Your program does not only define:
- Data types
- Functions
It also defines:
- Failure types
So the system becomes:
Valid State β Normal execution
Invalid State β Specific exception type
This is a powerful abstraction.
π§ͺ Adding Information to Custom Errors
Custom exceptions can carry data.
class TemperatureTooHighError(Exception):
def __init__(error, temperature):
message = f"Temperature {temperature}Β°C exceeds safe limit"
super().__init__(message)
error.temperature = temperature
Usage:
temp = 120
if temp > 100:
raise TemperatureTooHighError(temp)
Now the exception contains structured information.
You can access it later:
except TemperatureTooHighError as e:
print(e.temperature)
β οΈ Inheriting from Specific Exception Types
You donβt always need to inherit directly from Exception.
You can specialize existing categories.
Example:
class InvalidEmailError(ValueError):
pass
This communicates:
βThis is a ValueError β but more specific.β
This helps large systems where different error families exist.
πͺ Designing an Error Hierarchy
In complex projects, you can build error trees.
class AppError(Exception):
pass
class DatabaseError(AppError):
pass
class ConnectionError(DatabaseError):
pass
Benefits:
- Catch broad categories when needed
- Catch specific errors when necessary
Example:
try:
connect_to_db()
except DatabaseError:
print("Database problem detected")
π‘ Best Practices
1. Name errors with βErrorβ suffix
InvalidStateError
ConfigurationError
PermissionDeniedError
Consistency improves clarity.
2. Keep them lightweight
Most custom exceptions only need:
class MyError(Exception):
pass
Add complexity only when useful.
3. Use them to express contracts
Instead of vague checks:
if not valid:
raise Exception("Bad input")
Prefer:
if not valid:
raise InvalidConfigurationError("Missing API key")
𧬠Advanced Insight β Exceptions as Architecture
Custom exceptions help define system boundaries.
Each module can expose:
- Public functions
- Public error types
This creates a clean API:
Module API =
Functions
Exceptions
Professional libraries always do this.
β Summary
- Custom exceptions are classes that inherit from
Exception - They give semantic meaning to failures
- They improve debugging and architecture
- They can store additional data
- Large systems often define error hierarchies
Custom errors turn bugs into structured information instead of chaos.