๐ Day 14 : Exceptions
๐ฏ Enterprise Objective
Code will break. Networks fail, data is dirty, and files go missing. Professional code anticipates these failures. Today we learn how to gracefully catch exceptions, ensure resource cleanup, and signal bad state using custom errors.
๐ Strategic Overview
| # | Topic | Concept |
|---|---|---|
| 1 | Try/Except | Catching runtime errors |
| 2 | Else/Finally | Execution guarantees |
| 3 | Raising | Custom error signaling |
1. Try / Except Blocks : Catching Errors
Code crashes when an error occurs. A try/except block allows you to 'catch' exceptions, handle them gracefully, and let the program continue running. This is critical for data pipelines where one bad row shouldn't stop the whole job.
try:
result = 10 / 0
except ZeroDivisionError:
result = 0
๐ผ Why Data Analysts Care
โข API Calls: Handling network timeouts or 500 errors without crashing
โข Data Cleaning: Catching ValueError when trying to cast the string 'N/A' to an integer
โ ๏ธ The Bare Except Trap
Never write a bare except:. It will catch SystemExit and KeyboardInterrupt (Ctrl+C), making your program impossible to stop. ALWAYS specify the exception type: except Exception as e: at a minimum.
๐งช Concept Checks: Try/Except
Q1. Write a try/except block that catches ZeroDivisionError when calculating 10 / 0. Print "Cannot divide by zero".
Q2. Try accessing d = {"a": 1}; print(d["b"]). Catch the KeyError and print a warning.
Q3. Use except Exception as e: to catch ANY error in 1 + "a". Print e to see the actual error message.
Q4. Write a try/except block to catch FileNotFoundError when trying to open "fake.txt". Print "File not found".
Q5. Why is a bare except: dangerous? Give an example of an exception it might catch that you don't want it to.
2. Else and Finally : Complete Error Control
The full exception block includes else and finally. else runs only if no exception occurred. finally runs no matter what (even if the program crashes or returns early).
| Block | When does it run? | Common Use |
|---|---|---|
try | Always | Risky code |
except | If error happens | Error handling & logging |
else | If NO error happens | Code that depends on the try block succeeding |
finally | ALWAYS | Cleaning up resources (closing files/connections) |
๐ผ Why Data Analysts Care
โข Database Connections: Ensuring db.close() runs in the finally block even if the query fails
โข Safe Transactions: Committing a database transaction in the else block only if the execution succeeds
๐ง Pro Tip
Keep the try block as small as possible. Put the risky code in try, and the code that depends on it succeeding in the else block.
๐งช Concept Checks: Else & Finally
Q1. Write a try/except/else block. Try val = int("10"). In else, print "Success: [val]". Test it.
Q2. Change the input in Q1 to "abc". Watch the except run and the else skip.
Q3. Add a finally block to Q1 that prints "Cleanup complete". Run it with both "10" and "abc".
Q4. Write a function that opens a file in the try, and closes it in the finally. (Simulate with print statements).
Q5. Explain why code belonging in the else block shouldn't just be put at the bottom of the try block.
3. Raising Exceptions : Signaling Invalid State
Sometimes your code detects an invalid state (like a negative age). You can manually trigger an error using the raise keyword. This tells the calling function that something went wrong.
def set_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
return age
๐ผ Why Data Analysts Care
โข Data Validation: Rejecting dirty data explicitly before it poisons the database
โข API Contracts: Raising 400 Bad Request errors when user input is invalid
๐ง Pro Tip
You can define custom exceptions by subclassing Exception: class DataValidationError(Exception): pass. This makes your error handling highly specific.
๐งช Concept Checks: Raising Errors
Q1. Write a function divide(a, b) that explicitly raises a ValueError with message "b cannot be zero" if b == 0.
Q2. Create a custom exception class InvalidAgeError(Exception): pass. Raise it if age is > 150.
Q3. Write a try/except block to catch your InvalidAgeError from Q2 and print a friendly message.
Q4. What is the difference between raise Exception("msg") and raise ValueError("msg")?
Q5. Catch an exception, log it, and then re-raise it using the bare raise keyword. Demonstrate this.
๐ ๏ธ Professional Practice Tasks
Theory is useless without muscle memory. Complete these tasks to solidify your understanding.
Task 1 (Safe Config Loader): Write a function load_config(path) that tries to open a file. Catch FileNotFoundError and return a default dict. Catch json.JSONDecodeError and return {}. Finally, print 'Load attempt complete'.
Task 2 (Data Caster): Write a function safe_cast(val, to_type, default=None). Try to cast val to to_type. Catch ValueError and TypeError and return default.
Task 3 (Password Validator): Create custom exceptions LengthError and SpecialCharError. Write validate(pwd). Raise LengthError if < 8 chars. Raise SpecialCharError if no '!' or '@' is found. Test with try/except.
Task 4 (API Retry): Write a loop that tries to execute a flaky function connect(). If it raises ConnectionError, catch it, sleep 1 sec, and retry up to 3 times. If it succeeds, break. Else print 'Failed'.
Task 5 (File Cleanup): Write code that tries to create a file, write to it, and then explicitly raises an error. Use finally to ensure os.remove() is called on the file no matter what.
๐ป Pure Coding Interview Questions
Q1.
Write a try/except that catches Exception but lets KeyboardInterrupt pass through. Demonstrate by raising both and showing the different behaviors.
Q2.
Write two versions of a dict key lookup: one using if key in d (LBYL) and one using try/except KeyError (EAFP). Time both on 100,000 lookups and print the results.
Q3.
Write a loop that uses StopIteration via next(iter) to break. Then rewrite it using a standard for loop. Print results from both.
Q4.
Write code demonstrating else in a try block: try to open a file, use else to process it only if the open succeeded.
Q5.
Write a try/except that catches both ValueError and TypeError in a single except clause. Test with int('abc') and len(5).
Q6.
Write code showing that if a try block returns 1 and a finally block returns 2, the function returns 2. Demonstrate this behavior.
Q7.
Write a context manager class Timer with enter and exit that prints how long the with block took to execute.
Q8.
Write code that catches an exception, prints the error message, then re-raises it using bare raise. Show the full traceback.
Q9.
Write code demonstrating raise ValueError('bad') from TypeError('original'). Print the chained exception's cause attribute.
Q10.
Write a function validate_email(s) that raises a custom InvalidEmailError if '@' is missing or the domain has no dot. Test with 3 inputs.
Q11.
Write code that raises an exception inside a finally block. Show what happens to the original exception (it gets suppressed).
Q12.
Write code comparing assert x > 0 vs if x <= 0: raise ValueError(). Show that assert can be disabled with -O flag.
Q13.
Write code that catches an exception and prints the full traceback string using traceback.format_exc() without crashing.
Q14.
Write code that uses contextlib.suppress(FileNotFoundError) to silently ignore a missing file. Show the equivalent try/except.
Q15.
Write code showing that a bare except: catches KeyboardInterrupt. Then fix it by using except Exception: instead.
Q16.
Write code that catches a SyntaxError from exec('if True print(1)') using try/except. Print the error message.
Q17.
Write a decorator @retry(max_attempts=3) that catches exceptions and retries a function up to 3 times. Test with a function that fails randomly.
Q18.
Write a helper function safe_get(lst, index, default=None) using try/except IndexError. Test with valid and invalid indices.
Q19.
Write code that uses sys.excepthook to log unhandled exceptions to a file before the program exits.
Q20.
Write a class Config that validates port (must be int 1-65535) and host (must be non-empty string) in init, raising TypeError/ValueError as appropriate.
Q21.
Write code using pd.to_numeric(series, errors='coerce') to convert ['10', 'bad', '20'] to numbers, replacing failures with NaN.
Q22.
Write a function validate_config(d) that checks for required keys ['host', 'port', 'db'] and raises ValueError immediately listing all missing keys.
Q23.
Write a function that parses nested JSON using EAFP: try to access data['users'][0]['email'], catching KeyError and IndexError.
Q24.
Write code that issues a DeprecationWarning using warnings.warn(). Then show how to catch it with warnings.catch_warnings().
Q25.
Write an exception class hierarchy: DatabaseError โ ConnectionError, QueryError. Raise and catch them at different levels.
๐ Day 14 Executive Summary
| # | Topic | Key Takeaway |
|---|---|---|
| 1 | Try | Wrap risky code to prevent crashes |
| 2 | Finally | Always runs, perfect for db.close() |
| 3 | Raise | Use explicit errors to reject bad data |
โ Instructor's End-of-Day Checklist
โข [ ] I can write a try/except block.
โข [ ] I never use a bare except:.
โข [ ] I understand when to raise an error.