Errors Are Just Values With a Bad Attitude
When something goes wrong in Python — dividing by zero, reading a missing file, parsing a bad number — the runtime creates an exception object and starts unwinding the call stack until something catches it. If nothing does, your program terminates and prints a traceback.
Exceptions aren't themselves bad. They're how Python signals "I can't continue with this operation; here's why." Your job is to decide, case by case, which ones you know how to recover from and which you should let escalate.
The Basic Shape
try:opens the block of risky code.except ValueError:catches that specific exception if it's raised inside thetry.- If no exception fires, the
exceptis skipped entirely.
Run the snippet with 42 — works. Run it with hello — the handler runs.
Catching Specific Exceptions
Python has a hierarchy of exception types. A few you'll meet often:
ValueError— a value was wrong in some way (int("abc"), out-of-range arguments).TypeError— the wrong type was used ("hi" + 3).KeyError— a dict key wasn't found.IndexError— a sequence index was out of range.FileNotFoundError— a file doesn't exist.ZeroDivisionError— tried to divide by zero.AttributeError— an object doesn't have the requested attribute.
Catch the specific one you know how to handle:
You can catch multiple exceptions in one clause by passing a tuple:
Notice as e. That binds the exception object to e so you can inspect its message or attributes.
Avoid Catching Everything
A bare except: catches literally anything, including KeyboardInterrupt (your Ctrl-C) and system-level exits. Don't use it.
except Exception: is a little better, but still dangerous — it swallows bugs you didn't anticipate and hides the true source of problems:
# Don't do this without a really good reason.
try:
do_something()
except Exception:
pass
The right move is almost always to catch the specific exception you know how to recover from. If an exception you didn't expect reaches the top of your program, the traceback tells you exactly what went wrong — that's a feature, not a bug.
else and finally
The try statement has two more optional clauses:
elseruns if thetryblock finished without raising.finallyruns no matter what — exception or not.
else is the cleanest place for "success only" code — you don't want a try block doing more than the part that can actually fail. finally is for cleanup that must run even if the try fails: closing a resource, releasing a lock, restoring state.
Raising Exceptions Yourself
Use raise to signal an error in your own code:
Pick an exception type that fits what went wrong. Reach for the built-ins first — ValueError, TypeError, FileNotFoundError — before defining your own class.
Defining Your Own Exception
When the built-ins don't capture the meaning, define a custom exception:
Inherit from Exception (or a more specific built-in) and give the class a docstring. That's usually all you need. Custom exceptions let callers catch just the error meaningful to their domain.
raise ... from ...: Chained Exceptions
When one exception triggers another, keep the chain:
The from e attaches the original error. When the traceback prints, Python shows both — the ConfigError that surfaced and the FileNotFoundError that caused it. That kind of trail is invaluable when debugging.
Context Managers: The Cleaner Way to Clean Up
finally is fine, but for resources like files, a context manager (what with uses) is almost always better:
# finally version
f = open("data.txt")
try:
data = f.read()
finally:
f.close()
# with version
with open("data.txt") as f:
data = f.read()
Both are safe. The with form is shorter and applies automatically. Reach for finally only when you're doing something the standard library doesn't already wrap in a context manager.
When Not to Catch
Catching an exception is a decision — you're saying "I can handle this." If you can't, let the exception propagate. Code like this is almost always a mistake:
try:
do_work()
except Exception:
pass # silently ignore everything
Silencing errors makes bugs invisible. It's better to crash loudly than to limp along in an inconsistent state.
Wrap Up
try/exceptlets you handle errors you can recover from.- Catch specific exceptions, not
Exceptionblankly. raisesignals errors in your own code.withblocks replace mostfinallycleanup.- When in doubt, let the exception propagate.
Next: a tour of the specific errors Python raises most often — KeyError, ValueError, ModuleNotFoundError, and a few more — plus the debugging habits that fix them quickly.
Frequently Asked Questions
How do I handle errors in Python?
Wrap risky code in a try block and catch the specific exception in an except block: try: risky() except ValueError: .... Optional else runs if no exception occurred; finally runs either way, for cleanup.
Should I catch Exception to be safe?
No. A bare except: or except Exception: hides bugs you didn't anticipate. Catch the specific exception you know how to recover from, and let everything else propagate so you see the real problem.
What's the difference between raise and raise from?
raise NewError(...) raises a new exception. raise NewError(...) from original keeps the original exception attached as the cause, which Python shows in the traceback. Use from when a lower-level error triggered a higher-level one you want to surface.