Python · Syntax · Beginner
Error Handling Basics
Learn how to handle errors gracefully using try/except/else/finally, raise your own exceptions, and write robust Python programs.
Quick topic start and explanations before exercises (exercises below):
Error Handling Patterns
#Error Handling Reference
#Exercises:
Safe Convert to Int
#Write a function `safe_int(s, default=0)` that tries to convert `s` to an integer and returns it on success, or `default` if conversion fails. It should never raise an exception. Example: `safe_int("42")` → `42`, `safe_int("abc")` → `0`, `safe_int("abc", -1)` → `-1`.
def safe_int(s, default=0):
pass
print(safe_int("42")) # 42
print(safe_int("abc")) # 0
print(safe_int("abc", -1)) # -1
Solution
def safe_int(s, default=0):
try:
return int(s)
except (ValueError, TypeError):
return default
print(safe_int("42")) # 42
print(safe_int("abc")) # 0
print(safe_int("abc", -1)) # -1
Safe Division
#Write a function `safe_divide(a, b)` that returns `a / b` as a float, or `None` if `b` is zero. Use try/except. Example: `safe_divide(10, 2)` → `5.0`, `safe_divide(10, 0)` → `None`.
def safe_divide(a, b):
pass
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # None
Solution
def safe_divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # None
Multiple Exception Types
#Write a function `parse_value(s)` that: - Returns `int(s)` if `s` can be converted to an integer - Returns `float(s)` if it can be converted to float but not int - Returns `None` if neither conversion works Do not use `.` to detect floats — use try/except only.
def parse_value(s):
pass
print(parse_value("42")) # 42 (int)
print(parse_value("3.14")) # 3.14 (float)
print(parse_value("abc")) # None
Solution
def parse_value(s):
try:
return int(s)
except ValueError:
pass
try:
return float(s)
except ValueError:
return None
print(parse_value("42")) # 42
print(parse_value("3.14")) # 3.14
print(parse_value("abc")) # None
Validate Age
#Write a function `validate_age(age)` that: - Raises `TypeError` if `age` is not an integer - Raises `ValueError` with message `"Age must be between 0 and 150"` if age is out of that range - Returns `age` if valid Then write code that calls `validate_age` with user input and handles both exceptions with appropriate messages.
def validate_age(age):
pass
print(validate_age(25)) # 25
try:
validate_age("old")
except TypeError as e:
print(e)
try:
validate_age(200)
except ValueError as e:
print(e)
Solution
def validate_age(age):
if not isinstance(age, int):
raise TypeError(f"Expected int, got {type(age).__name__}")
if not 0 <= age <= 150:
raise ValueError("Age must be between 0 and 150")
return age
print(validate_age(25)) # 25
try:
validate_age("old")
except TypeError as e:
print(e)
try:
validate_age(200)
except ValueError as e:
print(e)
Use else Clause
#Write a function `read_number(s)` that: - Tries to convert `s` to a float - If conversion fails, prints `"Invalid input"` and returns `None` - If it succeeds (use `else`), prints `"Got: {value:.2f}"` and returns the float The print in the success case must be inside the `else` block, not inside `try`.
def read_number(s):
pass
print(read_number("3.14")) # prints "Got: 3.14", returns 3.14
print(read_number("abc")) # prints "Invalid input", returns None
Solution
def read_number(s):
try:
value = float(s)
except ValueError:
print("Invalid input")
return None
else:
print(f"Got: {value:.2f}")
return value
print(read_number("3.14")) # Got: 3.14
print(read_number("abc")) # Invalid input
Finally Cleanup
#Write a function `safe_open(path)` that: - Opens a file at `path` and reads its content - Returns the content as a string if successful - Returns an empty string if `FileNotFoundError` occurs - Always prints `"Closing file"` in a `finally` block, whether or not opening succeeded (You can simulate opening with a variable that might be None.)
def safe_open(path):
pass
print(safe_open("/tmp/test_eh.txt")) # content or ""
print(safe_open("/tmp/no_such_file.txt")) # ""
Solution
def safe_open(path):
f = None
try:
f = open(path, encoding="utf-8")
return f.read()
except FileNotFoundError:
return ""
finally:
print("Closing file")
if f:
f.close()
import pathlib
pathlib.Path("/tmp/test_eh.txt").write_text("hello", encoding="utf-8")
print(safe_open("/tmp/test_eh.txt"))
print(safe_open("/tmp/no_such_file.txt"))
Access Nested Dict Safely
#Write a function `get_user_city(data, username)` that retrieves `data[username]["address"]["city"]`. Handle `KeyError` if any key is missing and return `"Unknown"` in that case. Example: `get_user_city({"alice": {"address": {"city": "Kyiv"}}}, "alice")` → `"Kyiv"`, `get_user_city({}, "bob")` → `"Unknown"`.
def get_user_city(data, username):
pass
users = {"alice": {"address": {"city": "Kyiv"}}}
print(get_user_city(users, "alice")) # Kyiv
print(get_user_city(users, "bob")) # Unknown
Solution
def get_user_city(data, username):
try:
return data[username]["address"]["city"]
except KeyError:
return "Unknown"
users = {"alice": {"address": {"city": "Kyiv"}}}
print(get_user_city(users, "alice")) # Kyiv
print(get_user_city(users, "bob")) # Unknown
Raise with Validation
#Write a function `create_username(name)` that: - Raises `ValueError` if `name` is empty or contains only whitespace - Raises `ValueError` if `name` is longer than 20 characters - Raises `ValueError` if `name` contains spaces - Otherwise returns `name.lower().strip()` Include a clear error message in each `ValueError`.
def create_username(name):
pass
print(create_username("Alice")) # alice
print(create_username(" Bob ")) # bob
try:
create_username("")
except ValueError as e:
print(e)
Solution
def create_username(name):
name = name.strip()
if not name:
raise ValueError("Username cannot be empty")
if len(name) > 20:
raise ValueError("Username must be 20 characters or less")
if " " in name:
raise ValueError("Username cannot contain spaces")
return name.lower()
print(create_username("Alice")) # alice
print(create_username(" Bob ")) # bob — strip() removes outer spaces
try:
create_username("")
except ValueError as e:
print(e)
Chain of Operations
#Write a function `process(text)` that performs three operations in sequence: 1. Convert `text` to uppercase 2. Split on comma and take the first element 3. Convert the first element to an integer Handle `ValueError`, `IndexError`, and `AttributeError` in a single `except` clause and return `None` on any error. Return the integer on success.
def process(text):
pass
print(process("hello,42,world")) # 42
print(process("no-comma")) # None
print(process("word,abc")) # None
print(process(None)) # None
Solution
def process(text):
try:
upper = text.upper()
first = upper.split(",")[0]
return int(first)
except (ValueError, IndexError, AttributeError):
return None
print(process("hello,42,world")) # None (first part is "HELLO", not int)
print(process("42,extra")) # 42
print(process(None)) # None
Exception Report
#Write a function `run_all(functions, input_value)` that takes a list of functions and runs each one on `input_value`. It should return a list of results where each element is either the return value (if the function succeeded) or the string `"ERROR: {exception_message}"` if any exception was raised. Example: with `functions = [str, int, lambda x: x/0]` and `input_value = "42"`, result is `["42", 42, "ERROR: division by zero"]`.
def run_all(functions, input_value):
pass
funcs = [str, int, lambda x: x / 0]
print(run_all(funcs, "42"))
# ["42", 42, "ERROR: division by zero"]
Solution
def run_all(functions, input_value):
results = []
for fn in functions:
try:
results.append(fn(input_value))
except Exception as e:
results.append(f"ERROR: {e}")
return results
funcs = [str, int, lambda x: x / 0]
print(run_all(funcs, "42"))
# ["42", 42, "ERROR: division by zero"]