Python with AI Tutorial · Chapter 23 of 48
Python try except is the mechanism for handling errors without crashing your program. Code that might fail goes in a try block; if an exception occurs, Python jumps to the matching except block instead of stopping. Optional else and finally clauses run when no error occurred and when the block finishes, respectively.
Why exceptions matter
When something goes wrong, such as dividing by zero or opening a missing file, Python raises an exception. If nothing catches it, the program prints a traceback and exits. In a report that processes hundreds of records, one bad row should not throw away the other 499.
revenue = 12000
units_sold = 0
print(revenue / units_sold)
Output:
Traceback (most recent call last):
File "report.py", line 3, in <module>
print(revenue / units_sold)
ZeroDivisionError: division by zero
The last line of a traceback names the exception type and gives a message. Read tracebacks from the bottom up: the type tells you what category of problem occurred, and the lines above show where.
Basic try…except
Wrap the risky code in try and name the exception you expect in except. If that exception is raised, the handler runs and the program continues.
revenue = 12000
units_sold = 0
try:
price = revenue / units_sold
print(f"Average price: {price:.2f}")
except ZeroDivisionError:
print("No units sold this period, average price not available")
print("Report continues")
Output: No units sold this period, average price not available Report continues
Notice that the print() inside the try block did not run either. As soon as the division fails, Python abandons the rest of the try block and looks for a handler. Anything you want to run only on success belongs in the else clause, covered below.
Common built-in exceptions
Python has dozens of exception types. These are the ones you will meet most often in data and business scripts.
| Exception | Raised when | Typical example |
|---|---|---|
ValueError |
Right type, wrong value | int("12.5kg") |
TypeError |
Operation on an incompatible type | "5" + 3 |
KeyError |
Dictionary key missing | prices["Desk"] |
IndexError |
List index out of range | items[10] on a 3-item list |
ZeroDivisionError |
Dividing by zero | total / 0 |
FileNotFoundError |
File path does not exist | open("missing.csv") |
AttributeError |
Object lacks that attribute or method | None.upper() |
Handling several exception types
You can stack multiple except blocks, or catch several types in one block with a tuple. Python uses the first handler that matches, so put the most specific ones first.
raw_values = ["1200", "abc", "850", None, "430"]
total = 0
for raw in raw_values:
try:
total += int(raw)
except ValueError:
print(f"Skipping non-numeric value: {raw!r}")
except TypeError:
print("Skipping missing value")
print("Total:", total)
Output: Skipping non-numeric value: 'abc' Skipping missing value Total: 2480
Getting the error message with as
Add as e to keep the exception object. Printing it shows the message, which is often exactly what you want to log.
prices = {"Chair": 1450, "Desk": 3900}
for item in ["Chair", "Lamp"]:
try:
print(item, prices[item])
except KeyError as e:
print(f"Price list has no entry for {e}")
Output: Chair 1450 Price list has no entry for 'Lamp'
except: with no type catches everything, including KeyboardInterrupt and typos in your own variable names. It hides real bugs. Always name the exception, or at minimum use except Exception as e: and log e.else and finally
The else block runs only when the try block raised nothing. The finally block runs no matter what, which makes it the right place for clean-up such as closing files or connections.
def load_rate(rates, currency):
try:
rate = rates[currency]
except KeyError:
print(f"{currency}: no rate, using 1.0")
rate = 1.0
else:
print(f"{currency}: rate found")
finally:
print("Lookup finished")
return rate
rates = {"USD": 83.5, "EUR": 90.2}
print(load_rate(rates, "EUR"))
print(load_rate(rates, "GBP"))
Output: EUR: rate found Lookup finished 90.2 GBP: no rate, using 1.0 Lookup finished 1.0
Paste a traceback you do not understand and ask the assistant to explain it line by line and to suggest the narrowest except clause that would handle it.
Here is a Python traceback from my invoice script: [paste traceback]. Explain what each line means, reading from the bottom up, tell me which exception type I should catch and why catching Exception or a bare except would be a bad idea here, and show the corrected try/except block.
In practice, finally is how you guarantee resources are released. If a function opens a database connection or a file and then raises halfway through, the finally block still closes it. The with statement, introduced in the file-handling chapter, is built on the same idea.
Raising your own exceptions
Use raise to signal a problem yourself. Choose the built-in type that best describes the issue, and give a clear message. This turns silent bad data into an error the caller can handle.
def set_discount(percent):
if not 0 <= percent <= 100:
raise ValueError(f"Discount must be 0-100, got {percent}")
return percent / 100
for p in (15, 120):
try:
print(set_discount(p))
except ValueError as e:
print("Rejected:", e)
Output: 0.15 Rejected: Discount must be 0-100, got 120
Custom exception classes
For errors specific to your business rules, define your own exception by subclassing Exception. Callers can then catch exactly that problem and let unrelated errors bubble up.
class InsufficientStockError(Exception):
pass
def ship(item, qty, stock):
if stock[item] < qty:
raise InsufficientStockError(f"Only {stock[item]} {item} left, {qty} requested")
stock[item] -= qty
return stock[item]
stock = {"monitor": 5}
try:
ship("monitor", 8, stock)
except InsufficientStockError as e:
print("Order held:", e)
Output: Order held: Only 5 monitor left, 8 requested
Because InsufficientStockError inherits from Exception, a generic except Exception higher up would still catch it. The custom class simply gives you a more precise option.
Ask the assistant to add robust error handling to a loop that reads mixed-quality data, and to explain why each exception is handled where it is.
Write a Python function parse_orders(rows) that takes a list of dictionaries with keys "id", "qty" and "price" as strings. Convert qty to int and price to float, skip rows with missing keys or bad numbers, collect the failures in a separate list with a reason, and return (good_rows, failures). Use specific except clauses for KeyError, ValueError and TypeError and explain why you did not use a bare except.
A good habit is to keep the try block as small as possible: only the one or two lines that can actually fail. This makes it obvious which operation the handler is protecting and stops unrelated bugs from being caught by accident.
Common mistakes
- Using a bare
except:that swallows every error, including programming mistakes. - Wrapping far too much code in one
try, so you cannot tell which line failed. - Catching an exception and doing nothing (
pass) so the failure disappears silently. - Catching
Exceptionbefore a more specific handler, which makes the specific handler unreachable. - Using exceptions for normal control flow when a simple
ifcheck would be clearer.
Exercise
Write a function safe_average(values) that returns the average of a list of numbers. It should return None and print a message if the list is empty (ZeroDivisionError) and skip any item that cannot be converted to float. Test it with ["10", "20", "x", "30"] and [].
Show answer
def safe_average(values):
clean = []
for v in values:
try:
clean.append(float(v))
except (ValueError, TypeError):
print(f"Skipping {v!r}")
try:
return sum(clean) / len(clean)
except ZeroDivisionError:
print("No valid numbers")
return None
print(safe_average(["10", "20", "x", "30"]))
print(safe_average([]))
Output: Skipping 'x' 20.0 No valid numbers None
Related chapters
FAQ
What is the difference between except Exception and a bare except in Python?
except Exception catches ordinary runtime errors but lets system-level signals such as KeyboardInterrupt and SystemExit through. A bare except: catches those too, which can make a program impossible to stop cleanly.
When does the finally block run in Python?
Always: after the try block succeeds, after an except handler runs, and even if a return or an unhandled exception leaves the block. It is intended for clean-up work.
How do I raise an exception in Python?
Use raise followed by an exception instance, for example raise ValueError("Quantity must be positive"). You can also define your own class that inherits from Exception and raise that.
Working with spreadsheets too? Ready-made Excel, Google Sheets and Power BI templates are at NextGenTemplates.com.
Chapter 23 of 48 · Python with AI: all 48 chapters



