Errors are part of programming. Instead of crashing your program, Python lets you handle errors gracefully using try/except blocks. In this lesson, you’ll learn the difference between syntax and runtime errors, how exceptions work, how to use try, except, else, finally, and how to create custom exceptions.
1) Syntax Errors vs Runtime Errors
a) Syntax errors (caught before running)
A syntax error means your code breaks Python’s rules – usually a missing colon, bracket, or wrong indentation. Python can’t even start running it.
# SyntaxError: missing closing parenthesis
print("Hello"
When you run this, Python stops immediately and shows something like:
SyntaxError: '(' was never closed
b) Runtime errors (exceptions while running)
A runtime error happens while the program is running. The code is syntactically valid, but something goes wrong during execution.
x = 10
y = 0
result = x / y # ZeroDivisionError at runtime
ZeroDivisionError: division by zero
These runtime errors are called exceptions. Python gives you a chance to handle them instead of letting the program crash.
c) Logic errors (no exception, just wrong result)
A logic error is when your code runs without crashing but gives the wrong answer (for example, using + instead of *). Exceptions don’t help here – you fix these with testing and debugging.
2) What Is an Exception?
An exception is a special object that Python creates when something unexpected happens (like dividing by zero or opening a missing file). If you don’t handle it, Python:
- Stops the current function.
- Walks back up the call stack looking for a matching
exceptblock. - If none is found, the program terminates and prints a traceback.
def divide(a, b):
return a / b
print("Before")
result = divide(10, 0) # raises ZeroDivisionError
print("After") # never reached
To prevent the crash, you wrap risky code in try/except.
3) Basic try / except
Use try to “attempt” some code, and except to handle what happens if it fails.
try:
x = int(input("Enter a number: "))
result = 100 / x
print("Result:", result)
except ZeroDivisionError:
print("Cannot divide by zero.")
except ValueError:
print("Please enter a valid integer.")
Flow:
- If no error happens inside
try→ allexceptblocks are skipped. - If an exception happens → Python jumps to the first matching
exceptblock. - After handling, the program continues after the whole
try/exceptblock.
a) Catching any exception (use carefully)
try:
risky_operation()
except Exception as e:
print("Something went wrong:", e)
Catching Exception is useful for logging or as a last resort, but for most cases you should prefer specific exceptions (ValueError, FileNotFoundError, etc.).
4) else and finally
a) else — runs only if no exception happened
The else block runs when the try block succeeds without raising an exception.
try:
x = int(input("Enter a positive number: "))
except ValueError:
print("That was not a number.")
else:
print("You entered:", x)
# safe to use x here as a valid int
b) finally — runs no matter what
The finally block always runs — whether an exception happened or not. Use it for cleanup: closing files, releasing resources, etc.
file = None
try:
file = open("data.txt", "r", encoding="utf-8")
content = file.read()
print("File length:", len(content))
except FileNotFoundError:
print("data.txt was not found.")
finally:
if file is not None:
file.close()
print("File closed.")
Even if an exception occurs, finally will still run.
5) Common Built-in Exceptions
Some exceptions you’ll see often:
ValueError– wrong value (e.g.,int("abc")).TypeError– wrong type (e.g.,"2" + 3).ZeroDivisionError– division by zero.FileNotFoundError– file/path doesn’t exist.IndexError– list index out of range.KeyError– missing key in a dict.
data = {"name": "Ammar"}
try:
age = data["age"] # KeyError
except KeyError:
print("Key 'age' is missing, using default.")
age = 0
6) Raising Exceptions Yourself
Sometimes you want to signal that something is wrong even if Python doesn’t automatically raise an error. Use raise.
def withdraw(balance, amount):
if amount <= 0:
raise ValueError("Amount must be positive.")
if amount > balance:
raise ValueError("Insufficient funds.")
return balance - amount
try:
new_balance = withdraw(1000, 1500)
except ValueError as e:
print("Withdrawal failed:", e)
Here you’re defining your own rules and using exceptions to enforce them.
7) Custom Exceptions
For larger programs, it’s useful to define your own exception types. This makes error handling more descriptive and easier to manage.
class InsufficientFundsError(Exception):
"""Raised when an account has not enough balance for an operation."""
pass
class BankAccount:
def __init__(self, owner: str, balance: float = 0.0):
self.owner = owner
self.balance = balance
def withdraw(self, amount: float):
if amount <= 0:
raise ValueError("Amount must be positive.")
if amount > self.balance:
raise InsufficientFundsError(
f"Requested {amount}, but balance is only {self.balance}."
)
self.balance -= amount
return self.balance
# Using the custom exception
account = BankAccount("Ammar", balance=500)
try:
account.withdraw(800)
except InsufficientFundsError as e:
print("Custom error:", e)
You can catch your custom exception specifically, while still letting other errors bubble up or be handled separately.
8) Best Practices & Common Pitfalls
- Be specific: catch the exact exceptions you expect (
ValueError,FileNotFoundError, etc.), not justExceptioneverywhere. - Don’t swallow errors silently: avoid empty
except:blocks that do nothing; at least log or print something meaningful. - Use
finallyor context managers: for files and external resources, usefinallyorwithto ensure cleanup. - Raise early: validate inputs at the edges of your system and raise clear exceptions when something is invalid.
- Keep messages helpful: error messages should help the caller understand what went wrong and how to fix it.
Key Takeaways
- Syntax errors are caught before the program runs; runtime errors become exceptions during execution.
- Use
try/exceptto handle exceptions and keep your program from crashing. elseruns only when no exception occurs;finallyruns no matter what (good for cleanup).- Catch specific exceptions when possible; use broad catches like
Exceptiononly when you really need them. - You can raise exceptions yourself and define custom exception classes for clearer, domain-specific errors.
- Good error handling makes your programs safer, easier to debug, and more user-friendly.
In the next lesson, we’ll work with files and external data in Python Data Input & Output , where you’ll learn how to read/write text files, handle CSV and JSON, and safely work with real-world data sources.
Leave a comment
Your email address will not be published. Required fields are marked *
