Web Analytics

Assertions

Intermediate ~25 min read

The assert statement is a debugging aid that tests conditions during development. If a condition is true, nothing happens. If it's false, Python raises AssertionError. Unlike exceptions for handling errors, assertions catch bugs - they verify things that should NEVER be false if your code is correct!

Assert Statement Basics

The basic syntax is assert condition or assert condition, message. When the condition is true, nothing happens and execution continues. When false, Python raises AssertionError with your message. The key insight: assertions can be disabled with the -O flag, so never use them for critical checks!

Output
Click Run to execute your code
Assert Syntax:
assert condition - Raises AssertionError if condition is false
assert condition, message - Same, with custom error message

Internally equivalent to:
if __debug__ and not condition: raise AssertionError(message)

Writing Helpful Assert Messages

Always include a message with your assertions. Good messages include the actual values that caused the failure, not just what was expected. This makes debugging much easier. The message is only evaluated when the assertion fails, so computing it has no performance impact during normal execution.

Output
Click Run to execute your code
Good Assert Messages:
assert x > 0, f"x must be positive, got {x}" - Shows actual value
assert key in data, f"Missing {key} in {list(data.keys())}" - Shows context
assert result == expected, f"Expected {expected}, got {result}" - Shows both

Bad: assert x > 0 - No information on what went wrong!

When to Use Assertions

Use assertions for things that should NEVER be false if your code is correct - internal invariants, preconditions for internal functions, postconditions to verify results, and sanity checks in complex algorithms. Assertions document your assumptions and catch bugs during development.

Output
Click Run to execute your code
Use Assert For:
- Internal invariants (things that MUST be true)
- Development-time sanity checks
- Documenting assumptions in code
- Catching programmer errors (bugs)
- Verifying algorithm correctness

Key principle: Assertions catch BUGS, not bad input!

Assert Pitfalls - When NOT to Use

Never use assert for input validation, security checks, or anything that must work in production. Python's -O (optimize) flag disables all assertions! Also watch out for the tuple syntax bug: assert(condition, message) is always true because it's a non-empty tuple.

Output
Click Run to execute your code
NEVER Use Assert For:
- User input validation (use if + raise ValueError)
- Security/authentication checks (use proper auth exceptions)
- File/network operations (use proper error handling)
- Anything that could fail in production

Why? Running python -O removes ALL assertions!

Common Mistakes

1. Using assert for input validation

# WRONG - assert disabled with python -O!
def process_payment(amount):
    assert amount > 0, "Amount must be positive"  # DANGEROUS!
    charge_card(amount)

# CORRECT - always validate with if/raise
def process_payment(amount):
    if amount <= 0:
        raise ValueError("Amount must be positive")
    charge_card(amount)

2. The tuple bug - always true!

# WRONG - this ALWAYS passes!
x = -5
assert(x > 0, "x must be positive")  # (False, "msg") is truthy tuple!

# CORRECT - no parentheses
x = -5
assert x > 0, "x must be positive"  # Properly fails

# Or use explicit parentheses only around condition
assert (x > 0), "x must be positive"  # Also correct

3. Side effects in assert (removed with -O)

# WRONG - pop() doesn't happen with -O!
assert items.pop() == expected_item

# CORRECT - separate operation from assertion
item = items.pop()
assert item == expected_item

# WRONG - file never closes with -O!
assert file.close() is None

# CORRECT - close normally, then verify if needed
file.close()

4. No message (hard to debug)

# WRONG - no information on failure
assert user_id in valid_users

# CORRECT - include useful context
assert user_id in valid_users, \
    f"User {user_id} not found. Valid: {list(valid_users)[:5]}..."

# WRONG - message just restates condition
assert x > 0, "x must be greater than 0"

# CORRECT - show actual value
assert x > 0, f"x must be positive, got {x}"

5. Using assert for security checks

# WRONG - security bypassed with -O!
def delete_all_data(user):
    assert user.is_admin, "Only admins can delete"  # DANGEROUS!
    database.delete_all()

# CORRECT - always check permissions
def delete_all_data(user):
    if not user.is_admin:
        raise PermissionError("Only admins can delete")
    database.delete_all()

Exercise: Debug Helper

Task: Create a function with proper assertions for debugging.

Requirements:

  • Create a calculate_average function
  • Add assertions for internal invariants (not input validation)
  • Include helpful messages with actual values
  • Add a postcondition to verify the result
Output
Click Run to execute your code
Show Solution
def calculate_average(numbers):
    """
    Calculate average of a list of numbers.
    Uses assertions to catch bugs, not validate input.
    """
    # Input validation (NOT assertions - these are user-facing)
    if not isinstance(numbers, list):
        raise TypeError(f"Expected list, got {type(numbers).__name__}")
    if len(numbers) == 0:
        raise ValueError("Cannot calculate average of empty list")

    # Calculate sum
    total = sum(numbers)

    # Internal invariant: length should still be positive
    n = len(numbers)
    assert n > 0, f"Bug: length became {n} after sum()"

    # Calculate average
    average = total / n

    # Postcondition: average should be within range of input values
    min_val = min(numbers)
    max_val = max(numbers)
    assert min_val <= average <= max_val, \
        f"Bug: average {average} outside range [{min_val}, {max_val}]"

    return average


# Test the function
test_cases = [
    [1, 2, 3, 4, 5],
    [10, 20, 30],
    [100],
    [-5, 0, 5],
]

print("=== Calculate Average Tests ===\n")
for numbers in test_cases:
    result = calculate_average(numbers)
    print(f"avg({numbers}) = {result}")

# Test error cases
print("\n=== Error Cases ===")
try:
    calculate_average([])
except ValueError as e:
    print(f"Empty list: {e}")

try:
    calculate_average("not a list")
except TypeError as e:
    print(f"Wrong type: {e}")

Summary

  • assert: assert condition, message - raises AssertionError if false
  • Purpose: Catch bugs during development, not handle errors
  • Good for: Internal invariants, postconditions, sanity checks
  • NOT for: Input validation, security checks, production checks
  • Messages: Always include actual values: f"got {x}"
  • -O flag: python -O disables ALL assertions
  • Tuple bug: assert(x, "msg") is always true!
  • Rule: If it could fail from user input, use raise not assert

What's Next?

Congratulations! You've completed the Error Handling module. You now know how to catch exceptions with try/except, clean up with finally, raise your own exceptions, create custom exception classes, and use assertions for debugging. Next, we'll move on to Object-Oriented Programming and learn how to create classes and objects!