Covers the exception hierarchy, handling, raising, and defining custom exceptions. Short and essential.
Python's error handling maps almost 1:1 to JS — with one powerful addition: the else clause, which runs only when no exception was raised.
try {
const data = JSON.parse(rawInput);
process(data);
} catch (err) {
if (err instanceof SyntaxError) {
console.error("Bad JSON:", err.message);
} else {
throw err; // re-throw
}
} finally {
cleanup();
}
import json
try:
data = json.loads(raw_input)
except json.JSONDecodeError as e:
print(f"Bad JSON: {e}")
except Exception as e:
raise # re-raise current exception
else:
# Runs ONLY if no exception was raised
process(data)
finally:
cleanup() # always runs
The else block runs only when the try block succeeds. This is cleaner than putting the success code inside try, because it avoids accidentally catching exceptions from that code. JS has no equivalent — use it freely.
Python exceptions are classes — all inherit from BaseException. Catch the most specific exception you can handle.
BaseException
├── SystemExit ← sys.exit() raises this
├── KeyboardInterrupt ← Ctrl+C
└── Exception ← catch-all for normal errors
├── ValueError ← bad value (int("abc"))
├── TypeError ← wrong type (1 + "a")
├── KeyError ← missing dict key (d["nope"])
├── IndexError ← list out of range (lst[99])
├── AttributeError ← missing attribute (None.upper())
├── FileNotFoundError
├── ZeroDivisionError
├── NameError ← undefined variable
├── ImportError
└── RuntimeError
# Multiple except clauses — most specific first
try:
result = risky()
except ValueError:
handle_value_error()
except (TypeError, AttributeError): # catch multiple in a tuple
handle_type_issues()
except Exception as e:
log_unexpected(e)
raise
try:
do_something()
except: # bare except — catches EVERYTHING including KeyboardInterrupt!
pass # silently swallows errors — dangerous
# Raise a built-in exception
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# Re-raise the current exception (in an except block)
try:
risky()
except Exception as e:
log(e)
raise # re-raises the same exception with original traceback
# Raise with chaining (show cause)
try:
int("abc")
except ValueError as e:
raise RuntimeError("Config parse failed") from e
Create custom exceptions by subclassing Exception. This is how you build domain-specific error types.
class AppError(Exception):
"""Base exception for this application."""
pass
class ValidationError(AppError):
def __init__(self, field, message):
self.field = field
self.message = message
super().__init__(f"{field}: {message}")
class NotFoundError(AppError):
def __init__(self, resource, id):
super().__init__(f"{resource} with id={id} not found")
# Usage
def get_user(user_id):
user = db.find(user_id)
if user is None:
raise NotFoundError("User", user_id)
return user
try:
user = get_user(42)
except NotFoundError as e:
print(e) # "User with id=42 not found"
except AppError:
print("Some app-level error")
Python culture prefers EAFP (Easier to Ask Forgiveness than Permission) over LBYL (Look Before You Leap). This is the opposite of many JS styles.
// Check before acting
if (obj && obj.user && obj.user.name) {
console.log(obj.user.name);
}
// Or optional chaining
console.log(obj?.user?.name);
# Just try it — handle the error if it happens
try:
print(obj["user"]["name"])
except (KeyError, TypeError):
print("Not available")
# OR use .get() for dicts specifically
name = obj.get("user", {}).get("name")
EAFP is more Pythonic and often faster — you don't pay for the checks on the happy path. Use it when failures are genuinely exceptional.
1. When does the else clause in a try/except run?
2. What does a bare raise (with no argument) do?
3. Accessing a missing key in a dict raises which exception?
4. What does EAFP mean in Python culture?
Questions answered correctly.