Week 11 Tutorial: Exception Handling
Problem 1: The Price Tag Cleaner
You are scraping data from an e-commerce website. The price data is messy: it comes as a list of strings. Some have currency symbols ("$10.50"), some represent free items ("Free"), and some are corrupted ("Error" or "NaN").
Write a function sum_valid_prices(price_list) that calculates the total cost.
- Iterate through the list.
- For each item:
- If the string is exactly
"Free"(case-insensitive), treat the price as0.0. - If the string contains a
"$"sign, remove it before attempting conversion. - Try to convert the cleaned string to a float.
- Except: If conversion fails (e.g., “Error”), print
"Skipping invalid price: [value]"and continue.
- If the string is exactly
- Return the sum of all valid prices.
Test Cases & Expected Output:
raw_prices = ["$12.50", "Free", "error_404", "$5.00", "2.50", "N/A"]
total = sum_valid_prices(raw_prices)
print(f"Total: ${total}")
Expected Output:
Skipping invalid price: error_404
Skipping invalid price: N/A
Total: $20.0
Problem 2: The Server Config Parser
You are parsing a configuration file for a game server. Each setting is a string in the format "setting_name:value" (e.g., "volume:80").
You receive a list of these strings.
Write a function parse_settings(config_lines) that returns a dictionary of valid settings.
- Iterate through the lines.
- Inside a
tryblock:- Split the string by
":". - Validate Structure: If the split result doesn’t have exactly 2 elements, raise an
IndexError. - Validate Type: Convert the value (the second part) to an integer.
- Validate Logic: The value must be between 0 and 100. If not, raise a
ValueErrorwith the message “Out of range”.
- Split the string by
- Except:
- Catch
IndexError: Print"Format error in: [line]". - Catch
ValueError: Print"Invalid value in: [line] ([reason])".
- Catch
- Return the dictionary of valid settings.
Test Cases:
configs = [
"volume:80", # Valid
"brightness:120", # Invalid Range
"difficulty:hard", # Invalid Type
"mute", # Invalid Format (no colon)
"contrast:50" # Valid
]
settings = parse_settings(configs)
print(f"Loaded Settings: {settings}")
Expected Output:
Invalid value in: brightness: 120 (Out of range)
Invalid value in: difficulty: hard (invalid literal for int() with base 10: 'hard')
Format error in: mute
Loaded Settings: {'volume': 80, 'contrast': 50}
Problem 3: The Safe Shopping Cart
You are building a checkout system.
inventory: A dictionary mapping item names to prices. Warning: Some items in the inventory have corrupted prices (set toNoneor negative numbers).cart: A list of item names a user wants to buy.
Write a function checkout(inventory, cart) that:
- Iterates through the
cart. - Try:
- Look up the item in
inventory. - If the price is
None, raise aTypeError(“Price missing”). - If the price is less than 0, raise a
ValueError(“Invalid price”). - Add the price to the
total_cost.
- Look up the item in
- Except:
- Handle
KeyError(Item not in store): Print"Item not found: [name]". - Handle
TypeError/ValueError(Bad data in store): Print"Data error for [name]: [reason]".
- Handle
- Count how many items failed to process.
- Return a tuple:
(total_cost, failed_count).
Test Cases:
store_db = {
"Apple": 0.50,
"Banana": 0.30,
"GhostItem": None, # Corrupt data
"GlitchItem": -5.00 # Corrupt data
}
my_cart = ["Apple", "Mango", "GhostItem", "Banana", "GlitchItem"]
cost, errors = checkout(store_db, my_cart)
print(f"Total: ${cost}, Errors: {errors}")
Expected Output:
Item 'Mango' not found
Data error for 'GhostItem': Price missing in database
Data error for 'GlitchItem': Negative price detected
Total: $0.8, Errors: 3
Problem 4: Bank Account with Overdraft Protection
Write a function withdraw_funds(account, amount) to manage withdrawals with strict overdraft rules.
account: A dictionary{'balance': float, 'limit': float}. (Limit is the max negative balance allowed, e.g., -100).amount: The amount to withdraw.
Logic & Exception Rules:
- Input Check: If
amountis not a number or is negative, raiseValueError(“Invalid amount”). - Calculation: Calculate
new_balance = current_balance - amount. - Validation:
- If
new_balanceis positive or zero: The transaction is safe. - If
new_balanceis negative but greater than or equal to the overdraftlimit: Raise aUserWarning(Note: standard Python exception) with message “Overdraft used”. The transaction should still happen! - If
new_balanceis less than the overdraftlimit: RaiseValueErrorwith message “Overdraft limit exceeded”. The transaction should NOT happen.
- If
Task:
Write a loop that attempts 3 transactions. Use try/except to:
- Catch
UserWarning: Print the warning but confirm the transaction happened. - Catch
ValueError: Print the error and confirm the transaction failed. - Catch success (via
else): Print “Transaction successful”. - Always print the remaining balance in
finally.
Test Cases:
my_account = {'balance': 50.0, 'limit': -100.0}
attempts = [40, 60, 200]
# 1. 50 - 40 = 10 (Safe)
# 2. 10 - 60 = -50 (Warning, but works)
# 3. -50 - 200 = -250 (Fail)
Expected Output:
Attempting to withdraw $40...
Transaction successful.
Current Balance: 10.0
Attempting to withdraw $60...
Alert: Overdraft used. Balance is -50.0
Transaction completed with warning.
Current Balance: -50.0
Attempting to withdraw $200...
Transaction Failed: Overdraft limit exceeded
Current Balance: -50.0
Problem 5: The "Atomic" Bank Transfer (Transaction Rollback)
In financial software, a transaction is considered “Atomic” if it either completely succeeds or completely fails. You cannot withdraw money from Person A, fail to deposit it to Person B, and leave the money in limbo. If the second step fails, you must “undo” (rollback) the first step.
Write a function perform_atomic_transfer(accounts, sender, receiver, amount) that manages this process.
The Data:
accounts is a dictionary: {"UserA": 100.0, "UserB": 50.0}.
The Rules:
- Validation Phase:
- If
senderdoesn’t exist, raiseKeyError(“Sender not found”). - If
senderhas insufficient funds, raiseValueError(“Insufficient funds”). - Do not check the receiver yet.
- If
- Withdrawal Phase:
- Deduct
amountfrom thesender’s balance. - Print
"Withdrew [amount] from [sender]".
- Deduct
- Deposit Phase (The Risk Zone):
- Try to add the money to the
receiver. - If
receiverdoes not exist in the dictionary, this operation will fail (you can simulate this by trying to accessaccounts[receiver]). - If it fails (KeyError), you must Catch the error, and perform a Rollback:
- Add the
amountback to thesender. - Print
"Error: Receiver not found. Refunding [sender]...". - Re-raise the exception (using
raise) so the main program knows the transfer failed.
- Add the
- Try to add the money to the
- If everything succeeds, return
True.
Test Script:
Create a main block with a try/except wrapper to test:
- A successful transfer (UserA -> UserB).
- A transfer to a non-existent user (UserA -> UserX). Verify UserA’s balance is restored.
- A transfer with insufficient funds.
Test Cases::
bank_db = {"Alice": 100.0, "Bob": 50.0}
Expected Output:
--- Test 1: Successful Transfer ---
-> Withdrew $30.0 from Alice.
-> Deposited $30.0 to Bob.
Transfer Complete.
Current State: {'Alice': 70.0, 'Bob': 80.0}
--- Test 2: Failed Transfer (Bad Receiver) ---
-> Withdrew $20.0 from Alice.
[!] Transaction failed: "Receiver 'Charlie' not found."
[!] Rolling back: Refunding $20.0 to Alice.
Main Error Handler: "Receiver 'Charlie' not found."
Current State: {'Alice': 70.0, 'Bob': 80.0}
--- Test 3: Insufficient Funds ---
Main Error Handler: Sender 'Bob' has insufficient funds.
Problem 6: The Smart Home Command Processor
You are building the control logic for a Smart Home Hub. Devices are controlled by sending text command strings like "LIGHT_LIVING:BRIGHTNESS:80".
You must parse these strings, validate every part of them, and update the device state.
Data Structure:
devices = {
"LIGHT_LIVING": {"type": "dimmer", "value": 50, "range": (0, 100)},
"THERMOSTAT": {"type": "temp", "value": 22, "range": (15, 30)},
"DOOR_LOCK": {"type": "binary", "value": 0, "range": (0, 1)} # 0=Locked, 1=Unlocked
}
Task:
Write a function process_commands(device_db, command_list) that iterates through a list of command strings.
The command format is strict: "DEVICE_ID:ACTION:VALUE".
Inside the loop, implement a robust try/except chain to handle:
- Format Errors: If the string doesn’t have exactly 3 parts, raise
ValueError(“Invalid command format”). - Unknown Device: If the ID isn’t in the DB, raise
KeyError(“Unknown device”). - Unknown Action: The only allowed action is
"SET". If anything else, raiseValueError(“Unsupported action”). - Value Type: The value must be an integer. If not, catch the conversion error (raise
ValueError). - Range Validation: Check the device’s specific min/max range tuple. If outside, raise
ValueError(“Value out of range”).
If an error occurs, print "Skipping command '[cmd]': [Reason]" and continue to the next command.
If successful, update the value in the dictionary and print "Updated [Device] to [Value]".
Test Input:
cmds = [
"LIGHT_LIVING:SET:100", # Valid
"LIGHT_LIVING:SET:200", # Out of range
"THERMOSTAT:COOL:20", # Invalid Action
"GARAGE_DOOR:SET:1", # Unknown Device
"DOOR_LOCK:SET:OPEN", # Invalid Value Type
"bad_format_command" # Bad Format
]
Expected Output:
--- Processing Smart Home Commands ---
Success: Updated LIGHT_LIVING to 100
Skipping command 'LIGHT_LIVING:SET:200': Value 200 outside allowed range 0-100
Skipping command 'THERMOSTAT:COOL:20': Unsupported action 'COOL'
Skipping command 'GARAGE_DOOR:SET:1': "Unknown device ID 'GARAGE_DOOR'"
Skipping command 'DOOR_LOCK:SET:OPEN': Value 'OPEN' must be an integer
Skipping command 'bad_format_command': Invalid command format (expected ID:ACTION:VALUE)
Final State: {
'LIGHT_LIVING': {'type': 'dimmer', 'value': 100, 'range': (0, 100)},
'THERMOSTAT': {'type': 'temp', 'value': 22, 'range': (15, 30)},
'DOOR_LOCK': {'type': 'binary', 'value': 0, 'range': (0, 1)}}
This content will be available starting December 09, 2025.