Chapter 6 Exercises
Comprehensive exercises covering exception handling in Python, sorted from easiest to hardest.
Exercises
Exercise 1: Safe List Access
Write a function safe_get that takes a list and an index. Use try-except to handle IndexError. If the index is valid, return the element. If an IndexError occurs, return None.
Safe List Access
def safe_get(my_list, index): try: return my_list[index] except IndexError: return NoneExercise 2: Safe Dictionary Access
Write a function safe_dict_get that takes a dictionary and a key. Use try-except to handle KeyError. If the key exists, return its value. If a KeyError occurs, return the string “Key not found”.
Safe Dictionary Access
def safe_dict_get(my_dict, key): try: return my_dict[key] except KeyError: return "Key not found"Exercise 3: Integer Conversion with Error Handling
Write a function safe_int that takes a value and tries to convert it to an integer. Handle ValueError and TypeError. If conversion succeeds, return the integer. If it fails, return None.
Integer Conversion with Error Handling
def safe_int(value): try: return int(value) except (ValueError, TypeError): return NoneExercise 4: Division with Multiple Exception Types
Write a function divide_numbers that takes two arguments and divides the first by the second. Handle ZeroDivisionError, TypeError, and ValueError separately, each printing a specific message. Return the result if successful, None otherwise.
Division with Multiple Exception Types
def divide_numbers(a, b): try: return a / b except ZeroDivisionError: print("Cannot divide by zero") return None except TypeError: print("Invalid type for division") return None except ValueError: print("Invalid value for division") return NoneExercise 5: List Operations with Exception Handling
Write a function process_list that takes a list and an operation (as a string: ‘sum’, ‘max’, ‘min’, ‘average’). Use try-except to handle cases where the list is empty (raise ValueError), contains non-numeric values (handle TypeError), or the operation is invalid (raise ValueError). Return the result of the operation.
List Operations with Exception Handling
def process_list(my_list, operation): try: if not my_list: raise ValueError("List is empty")
if operation == 'sum': return sum(my_list) elif operation == 'max': return max(my_list) elif operation == 'min': return min(my_list) elif operation == 'average': return sum(my_list) / len(my_list) else: raise ValueError("Invalid operation") except ValueError as e: print(f"Error: {e}") return None except TypeError: print("Error: List contains non-numeric values") return NoneExercise 6: Nested Try-Except Blocks
Write a function nested_access that takes a nested list of lists and two indices (row, col). Use nested try-except blocks to handle IndexError at both the outer list level and inner list level. In the outer try block, access the row. In the inner try block (inside the outer try), access the column. Return the element if found, None if either index is out of range.
Nested Try-Except Blocks
def nested_access(matrix, row, col): try: row_list = matrix[row] # Outer try: access row try: return row_list[col] # Inner try: access column except IndexError: return None # Column index out of range except IndexError: return None # Row index out of rangeExercise 7: Exception Handling with Comprehensions
Write a function safe_divide_list that takes two lists of numbers and returns a new list where each element is the division of corresponding elements from the two lists. Use a list comprehension with a helper function that handles ZeroDivisionError and IndexError. If division by zero occurs, use None. If index is out of range, use 0.
Exception Handling with Comprehensions
def safe_divide(a, b): try: return a / b except ZeroDivisionError: return None except IndexError: return 0
def safe_divide_list(list1, list2): def divide_at_index(i): try: return safe_divide(list1[i], list2[i]) except IndexError: return 0
return [divide_at_index(i) for i in range(max(len(list1), len(list2)))]Exercise 8: Custom Exception for Validation
Write a function validate_age that takes an age value. Try to convert it to an integer first. If conversion fails, raise a TypeError. If the age is negative or greater than 150, raise a ValueError. Then write code that calls this function and handles both exceptions, printing appropriate messages.
Custom Exception for Validation
def validate_age(age): try: age = int(age) except (ValueError, TypeError): raise TypeError("Age must be an integer") if age < 0: raise ValueError("Age cannot be negative") if age > 150: raise ValueError("Age cannot be greater than 150") return True
try: age_input = input() validate_age(age_input) print("Valid age")except TypeError as e: print(f"TypeError: {e}")except ValueError as e: print(f"ValueError: {e}")Exercise 9: Dictionary Operations with Exception Handling
Write a function dict_operations that takes a dictionary and performs multiple operations: gets a value by key, calculates the sum of all numeric values, and finds the maximum value. Use try-except blocks to handle KeyError, TypeError, and ValueError. Return a dictionary with results or error messages.
Dictionary Operations with Exception Handling
def dict_operations(my_dict, key): result = {}
# Get value by key try: result['value'] = my_dict[key] except KeyError: result['value'] = 'Key not found'
# Calculate sum try: numeric_values = [v for v in my_dict.values() if type(v) == int or type(v) == float] if numeric_values: result['sum'] = sum(numeric_values) else: result['sum'] = 'Cannot sum non-numeric values' except (TypeError, ValueError): result['sum'] = 'Cannot sum non-numeric values'
# Find max try: numeric_values = [v for v in my_dict.values() if type(v) == int or type(v) == float] if numeric_values: result['max'] = max(numeric_values) else: result['max'] = 'Cannot find max of non-numeric values' except (TypeError, ValueError): result['max'] = 'Cannot find max of non-numeric values'
return resultExercise 10: Try-Except-Else-Finally with Functions
Write a function process_data that takes a list of numbers and a divisor. Divide each number by the divisor and return a list of results. Use try-except-else-finally blocks. In the try block, perform all divisions. Handle ZeroDivisionError and TypeError by printing error messages. In the else block, print “All operations successful”. In the finally block, print “Processing complete”. Make sure the else block executes when no exceptions occur.
Try-Except-Else-Finally with Functions
def process_data(numbers, divisor): results = [] try: for num in numbers: results.append(num / divisor) except ZeroDivisionError: print("Cannot divide by zero") results = [] except TypeError: print("Invalid divisor type") results = [] else: print("All operations successful") finally: print("Processing complete") return resultsExercise 11: Complex Exception Handling with Comprehensions
Write a function safe_matrix_operations that takes a matrix (list of lists) and an operation (‘sum’, ‘max’, ‘min’). Use dictionary comprehension to create a mapping of row indices to their operation results. Handle IndexError, TypeError, and ValueError. If a row is empty or contains non-numeric values, map it to None.
Complex Exception Handling with Comprehensions
def safe_matrix_operations(matrix, operation): def process_row(row): try: if not row: return None numeric_values = [x for x in row if type(x) == int or type(x) == float] if not numeric_values: return None if operation == 'sum': return sum(numeric_values) elif operation == 'max': return max(numeric_values) elif operation == 'min': return min(numeric_values) else: raise ValueError("Invalid operation") except (TypeError, ValueError): return None
return {i: process_row(row) for i, row in enumerate(matrix)}Exercise 12: Exception Handling with Lambda and Map
Write a function safe_map_divide that takes two lists and a divisor. Use map() with a lambda function to divide each element of the first list by the divisor, then divide by corresponding elements from the second list. Handle ZeroDivisionError and IndexError. Return a list where errors are represented as None.
Exception Handling with Lambda and Map
def safe_map_divide(list1, list2, divisor): def safe_divide(x, y): try: return (x / divisor) / y except (ZeroDivisionError, IndexError): return None
return [safe_divide(list1[i], list2[i]) if i < len(list2) else None for i in range(len(list1))]Exercise 13: Nested Exception Handling with Functions
Write a function process_nested_data that takes a nested dictionary structure. Extract values from nested dictionaries, handle KeyError at multiple levels, and calculate statistics (sum, average) of numeric values found. Use try-except blocks at different nesting levels. Return a dictionary with statistics or error messages.
Nested Exception Handling with Functions
def process_nested_data(data): all_values = [] for key, nested_dict in data.items(): try: try: values = nested_dict['values'] if type(values) == list: all_values.extend([v for v in values if type(v) == int or type(v) == float]) except KeyError: pass except (TypeError, AttributeError): pass
if all_values: return { 'sum': sum(all_values), 'average': sum(all_values) / len(all_values), 'count': len(all_values) } else: return {'sum': 0, 'average': 0, 'count': 0}Exercise 14: Complex Exception Handling with Sorting
Write a function safe_sort_with_key that takes a list of dictionaries and a key function (as a lambda or function). Sort the list using the key function, but handle cases where the key doesn’t exist (KeyError), the value is not sortable (TypeError), or the list is empty. Return the sorted list or None if sorting fails.
Complex Exception Handling with Sorting
def safe_sort_with_key(data, key_func): try: if not data: return [] return sorted(data, key=key_func) except KeyError: return None except TypeError: return None except Exception: return NoneExercise 15: Ultimate Exception Handling Challenge
Write a function complex_data_processor that takes a list of mixed data (numbers, strings, lists, dictionaries). Use comprehensions, map, filter, and exception handling to: 1) Extract all numeric values from nested structures, 2) Calculate statistics (sum, max, min, average), 3) Handle all possible exceptions (TypeError, ValueError, KeyError, IndexError, AttributeError). Return a dictionary with results or error messages for each operation.
Ultimate Exception Handling Challenge
def extract_numbers(item): numbers = [] try: if type(item) == int or type(item) == float: numbers.append(item) elif type(item) == list: for subitem in item: numbers.extend(extract_numbers(subitem)) elif type(item) == dict: for value in item.values(): numbers.extend(extract_numbers(value)) except (TypeError, ValueError, KeyError, IndexError, AttributeError): pass return numbers
def complex_data_processor(data): all_numbers = [] for item in data: all_numbers.extend(extract_numbers(item))
try: if all_numbers: return { 'sum': sum(all_numbers), 'max': max(all_numbers), 'min': min(all_numbers), 'average': sum(all_numbers) / len(all_numbers) } else: return {'sum': 0, 'max': None, 'min': None, 'average': 0} except (TypeError, ValueError): return {'error': 'Cannot process data'}