Introduction to Programming - Error Handling and Debugging

Introduction to Programming Notes

Introduction

  • Instructor: Dr. Shounak Roychowdhury
  • Institution: School of Information, University of Texas at Austin

Recap of Previous Lecture

  • Review of:
    • Functions
    • Parameter Passing
    • Higher Order Functions
    • Lambdas
    • Recursion
    • Modules
    • File I/O

Today's Topic

  • Focus: Handling Error Scenarios
  • Goals:
    • Testing
    • Debugging

Error Handling Overview

  • Anticipate Error Scenarios: Handle them gracefully to maintain program stability.

Key Concepts in Error Handling

  • Errors and Exceptions
  • Exception Handling
    • Mechanism to manage unexpected events in a program.
    • Maintains program flow and prevents crashes.
  • Common Causes of Exceptions:
    • Invalid User Input
    • Code Errors
    • Device Failures
    • Network Connection Loss
    • Insufficient Memory
    • Division by Zero
    • Inaccessible Files
    • Race Conditions in Multi-threaded Systems

Exception Handling Process

  • General Flow:
    • Set of Statements
    • Create an Exception (using raise statement)
    • Exception Handler:
    • A sequence of operations that handles the error when it occurs.
    • Through this mechanism, program execution can resume post-exception handling.

Terminology in Exception Handling

  • Error Type:
    • Specific type of exception, e.g., ZeroDivisionError, KeyError, TypeError, NameError.
  • Traceback:
    • Sequence of function calls leading to the error, determining the faulty line.
  • Error Message:
    • Provides a textual explanation of what went wrong.

Reading Python Errors

  • Traceback Analysis:
    • Start reading from the bottom (the last line indicates the exact error).
    • Identify the problematic line number and file.
    • Understand the type of exception for insights into the error's nature.

Categories of Errors

  • Syntax Errors:

    • Errors due to incorrect format in Python statements.
    • Detected during translation to machine code before execution.
    • Example:
    mood = "I"m happy"
    
    • Causes SyntaxError: invalid syntax.
  • Runtime Errors:

    • Errors occur while the program is running (e.g., division by zero, accessing an out-of-bound list index).
    • Example:
      python 5/0 # raises ZeroDivisionError

Syntax Errors

  • Common Causes of Syntax Errors:
    • Missing colons in statements.
    • Incorrect indentation (leading to IndentationError).
    • Mixing tabs and spaces (TabError).

Runtime Errors

  • Examples of runtime errors include:
    • ZeroDivisionError: Raised during division by zero.
    • IndexError: Occurs when accessing a list index that doesn't exist.
    • NameError: Raised when accessing a variable that hasn't been defined.
    • ValueError: Raised for invalid literal operations (e.g., int('451.23')).

Exception Handling without Try/Except

  • Example Scenario: Division by zero without exception handling results in program termination.
deno = 0
nume = 15
frac = nume/deno  # ZeroDivisionError

Using Try/Except Blocks

  • Structure:
try:
    # code that might raise an exception
except ExceptionType:
    # code to handle the exception
  • Example:
try:
    frac = nume/deno
except ZeroDivisionError:
    print("cannot divide by zero")
  • Benefits: Prevents program termination due to unhandled exceptions.

Catching Specific Errors

  • Ability to handle specific exceptions differently;
    Example:
try:
    frac = nume/deno
except ZeroDivisionError:
    print(f"Cannot divide because deno={deno}")

Nested Try/Except Blocks

  • Allows handling exceptions at different levels of the code.
  • Example:
try:
    try:
        frac = nume/deno
    except ZeroDivisionError:
        print("Inner exception handling")
except Exception:
    print("Outer exception handling")

Using Finally Block

  • Finally Block: Code that runs after try and except regardless of success or failure.
    • Used for cleanup actions (e.g., closing files).

Raising Exceptions

  • Create custom error conditions using the raise statement.
  • Example:
if deno == 0:
    raise ValueError('Denominator cannot be zero.')

Error Chaining

  • Raising another exception in response to another allows keeping the context of the original error.
  • Use raise new_exception from old_exception syntax.

Built-in Errors

  1. NameError: Accessing an undeclared variable.
  2. TypeError: Passed an argument of inappropriate type.
  3. ValueError: Invalid value passed as an argument.
  4. FileNotFoundError: Attempting to open a non-existent file.
  5. IndexError: Accessing an index not in a list.

Exception Hierarchy

  • Base class: BaseException
  • Subcategories include:
    • Exception
    • ArithmeticError
    • RuntimeError
    • OSError
  • Hierarchical structure to classify and handle exceptions efficiently.

User-defined Exceptions

  • Custom exceptions can extend the Exception class.
  • Useful for grouping related error conditions or when standard exceptions aren't sufficient.

Testing and Debugging

  • Defensive Programming: Ensure code behaves as expected by checking assumptions and outputs.

Types of Testing

  • Unit Testing: Validate individual units (functions) of the program.
  • Regression Testing: Check previously fixed bugs.
  • Integration Testing: Validate combined parts of the system.

Debugging Techniques

  • Use print statements for temporary checks until issues are resolved.
  • Leverage tools like Python’s built-in debugger (pdb).
  • Follow established debugging procedures to identify bugs systematically.

Assertions

  • Assertion Statements: Verify assumptions during code execution. They raise an AssertionError if the condition fails.
  • Example:
assert len(grades) != 0, 'No grades data.'

Summary

  • Always handle exceptions explicitly to maintain control over program flow.
  • Keep code modular for easier testing and debugging.
  • Leverage Python’s tools for effective debugging.