Exception Handling
Exception Handling in Python
Even correct programs encounter problems while running: a user types letters where a number is expected, a file is missing, a network request fails, or a calculation divides by zero. When such an error occurs, Python raises an exception — and if nothing handles it, the program crashes and stops.
Exception handling lets you catch these runtime errors and respond gracefully — showing a helpful message, using a default value, or retrying — instead of letting the program crash.
Errors vs Exceptions
Python has two broad kinds of errors:
| Kind | When It Happens | Example | Can Be Handled? |
|---|---|---|---|
| Syntax error | Before the program runs, when Python reads the code | A missing colon or bracket | No — fix the code |
| Exception | While the program is running | Dividing by zero, a missing file | Yes — with try and except |
Common Built-in Exceptions
| Exception | Raised When |
|---|---|
NameError | A variable or name is not defined |
TypeError | An operation is used with the wrong type, such as "5" + 5 |
ValueError | A value has the right type but is invalid, such as int("abc") |
ZeroDivisionError | A number is divided by zero |
IndexError | A list index is out of range |
KeyError | A dictionary key does not exist |
FileNotFoundError | A file cannot be found |
The Exception Handling Blocks
Exception handling uses four blocks:
| Block | Purpose |
|---|---|
try | Contains code that might raise an error |
except | Runs if an error occurs in the try block |
else | Runs only if no error occurs |
finally | Runs always, whether an error occurred or not |
Syntax
try:
# code that might fail
except SomeError:
# code that handles the error
else:
# code that runs if no error occurred
finally:
# code that always runs
Only try and at least one except (or a finally) are required. else and finally are optional.
Basic Exception Handling
A Program Without Exception Handling
print(total)
print("This line never runs")
Output:
Traceback (most recent call last):
File "main.py", line 1, in <module>
print(total)
NameError: name 'total' is not defined
The program stops at the first line. The traceback shows where the error happened and what type it was.
Handling the Error With try and except
try:
print(total)
except NameError:
print("An error occurred: variable is not defined")
print("The program continues")
Expected output:
An error occurred: variable is not defined
The program continues
How it works:
- Python runs the
tryblock. print(total)raises aNameError.- Python immediately jumps to the matching
exceptblock, skipping any remaining lines intry. - After the
exceptblock finishes, the program continues normally.
Bare except vs Specific Exceptions
You may see a bare except: that catches every exception:
try:
print(total)
except:
print("Something went wrong")
This works, but it is risky: it also hides unexpected bugs and even catches attempts to stop the program with Ctrl + C. Always catch the specific exception you expect. If you need a general fallback, use except Exception: instead of a bare except:.
Handling Multiple Exceptions
Use several except blocks to handle different errors in different ways. Python checks them from top to bottom and runs the first one that matches.
try:
number = int("abc")
except ValueError:
print("Conversion failed: not a valid number")
except Exception:
print("An unexpected error occurred")
Expected output:
Conversion failed: not a valid number
int("abc") raises a ValueError, which matches the first block. The general except Exception: block acts as a safety net for any other error.
Order matters: Put specific exceptions before general ones. If
except Exception:came first, it would catch theValueError, and the specific message would never be shown.
Handling Several Exceptions in One Block
try:
result = 10 / int("0")
except (ValueError, ZeroDivisionError):
print("Please enter a non-zero number")
Expected output:
Please enter a non-zero number
Accessing the Error Message
Use as to give the exception a name and read its message:
try:
number = int("abc")
except ValueError as error:
print("Error details:", error)
Expected output:
Error details: invalid literal for int() with base 10: 'abc'
The else Block
The else block runs only if the try block finished without any exception.
try:
result = 10 / 2
except ZeroDivisionError:
print("Cannot divide by zero")
else:
print("Division successful:", result)
Expected output:
Division successful: 5.0
Why not put the print() inside try? Keeping the try block small — containing only the code that might fail — makes it clear which error you are handling, and prevents accidentally catching errors from unrelated code.
The finally Block
The finally block always runs — whether an exception occurred, was handled, or not. It is used for cleanup tasks such as closing files, closing database connections, or releasing other resources.
try:
value = int("20")
except ValueError:
print("Invalid input")
finally:
print("Execution completed")
Expected output:
Execution completed
With invalid input, the finally block still runs:
try:
value = int("twenty")
except ValueError:
print("Invalid input")
finally:
print("Execution completed")
Expected output:
Invalid input
Execution completed
All Four Blocks Together
def divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Error: division by zero")
else:
print("Result:", result)
finally:
print("Done\n")
divide(10, 4)
divide(10, 0)
Expected output:
Result: 2.5
Done
Error: division by zero
Done
Real-World Example: File Handling With Exceptions
The finally block ensures a file is closed, even if reading it fails. The file must be opened before the try block, so that finally only runs close() on a file that was actually opened:
try:
file = open("sample.txt", "r")
except FileNotFoundError:
print("File not found")
else:
try:
content = file.read()
print(content)
finally:
file.close()
If sample.txt does not exist, the output is:
File not found
Why not close the file in a single
finally? A common version of this example putsfile = open(...)insidetryandfile.close()infinally. If the file does not exist,open()fails, the variablefileis never created, andfile.close()infinallyraises a newNameError.
In modern Python, the with statement handles closing automatically and is the recommended approach:
try:
with open("sample.txt", "r") as file:
print(file.read())
except FileNotFoundError:
print("File not found")
The with statement closes the file as soon as its block ends, even if an error occurs. You will learn more in the File Handling lessons.
Raising Exceptions Manually
Your own code can raise exceptions with the raise keyword. This is how a function reports that it was given invalid data or cannot complete its task.
Raising a General Exception
score = -5
if score < 0:
raise Exception("Score cannot be negative")
Output:
Exception: Score cannot be negative
Raising a Specific Exception Type
It is better to raise the built-in exception type that best describes the problem, such as ValueError or TypeError.
age = "twenty"
if not isinstance(age, int):
raise TypeError("Age must be an integer")
Output:
TypeError: Age must be an integer
- Use
TypeErrorwhen a value is the wrong type. - Use
ValueErrorwhen a value is the right type but unacceptable (such as a negative age).
Practical Example: Validating and Handling Together
def set_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
return age
try:
set_age(-3)
except ValueError as error:
print("Invalid age:", error)
Expected output:
Invalid age: Age cannot be negative
The function raises the error when the rule is broken, and the calling code handles it. Separating the two keeps functions focused and lets each caller decide how to respond.
Custom Exceptions
For application-specific errors, define your own exception class by inheriting from Exception:
class InsufficientBalanceError(Exception):
pass
balance = 500
withdrawal = 800
try:
if withdrawal > balance:
raise InsufficientBalanceError("Not enough balance for this withdrawal")
except InsufficientBalanceError as error:
print(error)
Expected output:
Not enough balance for this withdrawal
Custom exceptions make error handling more precise and self-documenting. (Classes and inheritance are covered in the object-oriented programming lessons.)
Why Use Exception Handling?
- Prevents crashes — the program can recover from expected problems.
- Improves user experience — users see clear messages instead of tracebacks.
- Makes debugging easier — specific exceptions and messages pinpoint problems.
- Ensures clean resource management — files and connections are always closed.
Common Mistakes
| Mistake | Problem |
|---|---|
Using a bare except: | Hides unexpected bugs; catch specific exceptions |
except Exception: before specific handlers | Specific handlers never run |
Putting too much code in try | Unclear which line caused the error |
Silently ignoring errors (except: pass) | Problems go unnoticed |
Using variables in finally that may not exist | Raises a new NameError |
| Using exceptions instead of simple checks | Use if for normal conditions; exceptions are for exceptional situations |
Related Concepts
- Python Type Casting — handling
ValueErrorfromint()andfloat() - Python File Handling — handling
FileNotFoundErrorand usingwith - Python Classes and Inheritance — creating custom exception classes