
Python
Learn practical methods to understand what's going wrong in your code.
When an exception occurs, Python shows you a traceback. Learn to read it:
# Example: A typical traceback
def process_user(user_dict):
age = int(user_dict["age"])
next_year_age = age + 1
return next_year_age
def main():
users = [
{"name": "Alice", "age": "25"},
{"name": "Bob"}, # Missing age!
]
for user in users:
result = process_user(user)
print(f"Next year age: {result}")
main()
# Output:
# Traceback (most recent call last):
# File "script.py", line 14, in <module>
# main()
# File "script.py", line 12, in main
# result = process_user(user)
# File "script.py", line 2, in process_user
# age = int(user_dict["age"])
# KeyError: 'age'
# Read it from bottom to top:
# 1. KeyError: 'age' - the actual error
# 2. In process_user() - which function
# 3. age = int(user_dict["age"]) - which line
# 4. Called from main() - where was it called fromSimple but effectiveβadd print statements to understand flow:
# Example: Finding where code breaks
def calculate_average(numbers):
print(f"Input: {numbers}")
total = sum(numbers)
print(f"Total: {total}")
count = len(numbers)
print(f"Count: {count}")
average = total / count
print(f"Average: {average}")
return average
# Test with problem input
result = calculate_average([]) # Empty list!
# Output shows that count is 0, leading to division by zero
# Input: []
# Total: 0
# Count: 0
# ZeroDivisionError: division by zero
# Fixed version with debugging
def calculate_average(numbers):
print(f"Starting calculation with {len(numbers)} numbers")
if not numbers:
print("WARNING: Empty list provided")
return None
total = sum(numbers)
print(f"Sum calculated: {total}")
average = total / len(numbers)
print(f"Average calculated: {average}")
return average
result = calculate_average([])
print(f"Result: {result}")
# Output:
# Starting calculation with 0 numbers
# WARNING: Empty list provided
# Result: NoneExtract and examine exception details:
# Access exception information in except block
import traceback
def risky_operation():
data = [1, 2, 3]
return data[10] # Index error
try:
risky_operation()
except IndexError as e:
# Get the exception message
print(f"Error: {e}")
# Get the exception type
print(f"Type: {type(e).__name__}")
# Get full traceback as string
print("Full traceback:")
print(traceback.format_exc())
# Output:
# Error: list index out of range
# Type: IndexError
# Full traceback:
# Traceback (most recent call last):
# File "script.py", line 8, in <module>
# ...Use assertions to catch logic errors:
# Assert stops execution if condition is false
def validate_age(age):
assert isinstance(age, int), "Age must be integer"
assert age >= 0, "Age cannot be negative"
assert age <= 150, "Age seems unrealistic"
return True
# Good input
validate_age(25) # Passes silently
# Bad input
try:
validate_age("25") # String instead of int
except AssertionError as e:
print(f"Assertion failed: {e}")
# Output: Assertion failed: Age must be integer
# Use in debugging
def process_payment(amount, balance):
# Sanity checks
assert amount > 0, "Amount must be positive"
assert balance >= 0, "Balance cannot be negative"
assert amount <= balance, "Insufficient funds"
return balance - amount
# Test
try:
new_balance = process_payment(150, 100)
except AssertionError as e:
print(f"Payment failed: {e}")
# Output: Payment failed: Insufficient funds# Step 1: Understand the error
# Read the traceback from bottom to top
# Step 2: Find the problem
def fetch_user_name(user_data):
# What if user_data is None?
return user_data["name"].upper()
try:
result = fetch_user_name(None)
except TypeError as e:
print(f"Error: {e}") # Output: 'NoneType' object is not subscriptable
# Step 3: Add debug info
def fetch_user_name_debug(user_data):
print(f"DEBUG: user_data = {user_data}")
print(f"DEBUG: type = {type(user_data)}")
if user_data is None:
print("DEBUG: user_data is None!")
return None
if "name" not in user_data:
print("DEBUG: 'name' key missing!")
return None
name = user_data["name"]
print(f"DEBUG: Found name = {name}")
return name.upper()
result = fetch_user_name_debug(None)
# Step 4: Fix the issue
def fetch_user_name_fixed(user_data):
if not user_data:
return None
return user_data.get("name", "Unknown").upper()The interactive debugger lets you pause and inspect code:
# Add pdb.set_trace() to pause execution
import pdb
def process_data(items):
result = []
for i, item in enumerate(items):
pdb.set_trace() # Execution pauses here
# Now you can inspect variables:
# > p item - print item
# > p i - print i
# > p result - print result
# > n - next line
# > c - continue
# > l - list code
processed = item * 2
result.append(processed)
return result
# Step through manually
data = [1, 2, 3]
# result = process_data(data)
# Better: use conditional breakpoint
def process_data_conditional(items):
for item in items:
if item == 2: # Only stop for specific value
import pdb; pdb.set_trace()
print(f"Processing {item}")
# data = [1, 2, 3]
# result = process_data_conditional(data)# β Bad: debug prints left in production code
def calculate(a, b):
print(f"DEBUG: a={a}, b={b}") # Never should go to production!
result = a + b
print(f"DEBUG: result={result}")
return result
# β
Better: use logging
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
def calculate_safe(a, b):
logger.debug(f"Calculating: a={a}, b={b}")
result = a + b
logger.debug(f"Result: {result}")
return result
# Logging output can be turned on/off by severity level
calculate_safe(5, 3)# Strategy 1: Verify assumptions
def find_user(users_list, user_id):
# Assumption: users_list is not empty
# Assumption: user_id exists in list
# Debug: check assumptions
print(f"DEBUG: Searching {len(users_list)} users for ID {user_id}")
for user in users_list:
if user["id"] == user_id:
return user
print(f"DEBUG: User {user_id} not found")
return None
# Strategy 2: Test one thing at a time
def complex_calculation(a, b, c):
step1 = a + b
print(f"Step 1: {a} + {b} = {step1}")
step2 = step1 * c
print(f"Step 2: {step1} * {c} = {step2}")
return step2
# Strategy 3: Simplify the test case
# Instead of running the full program, test the broken function in isolation
def buggy_function(data):
return data[0] + data[1]
# Create minimal test case
try:
result = buggy_function([]) # Empty list reveals the bug
except IndexError:
print("Index error with empty list!")
# Strategy 4: Binary search (comment out code)
# If you have lots of code, comment out the second half
# If error still happens, bug is in first half
# If error goes away, bug is in second half# Scenario 1: "It works sometimes"
# Usually indicates: uninitialized variables, type mismatch, or race conditions
def unreliable_function(items):
if len(items) > 0:
return items[0]
# BUG: What if items is None?
# Debug: add type check
def reliable_function(items):
if items is None:
print("DEBUG: items is None!")
return None
if len(items) == 0:
return None
return items[0]
# Scenario 2: "Wrong result"
# Usually indicates: logic error, wrong variable, or incorrect calculation
def calculate_discount(price, discount_percent):
# Bug: forgot to divide discount by 100
return price - discount_percent # Should be: discount_percent / 100
# Debug: print intermediate values
def calculate_discount_debug(price, discount_percent):
discount_amount = (price * discount_percent) / 100
print(f"DEBUG: Price={price}, Discount%={discount_percent}, Amount={discount_amount}")
return price - discount_amount
# Scenario 3: "Function returns None"
# Check: is there a return statement? Does every path return?
def get_greeting(time_of_day):
if time_of_day == "morning":
return "Good morning!"
elif time_of_day == "evening":
return "Good evening!"
# BUG: no return for other times!
# Debug: check all code paths
def get_greeting_fixed(time_of_day):
if time_of_day == "morning":
return "Good morning!"
elif time_of_day == "evening":
return "Good evening!"
else:
return "Hello!" # Handle all casesResources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep β’ Build Real β’ Verify Skills β’ Launch Forward