Debugging, Logging, and Best Practices

Section Overview

It’s not enough to write code that works — you need to write code that can be understood, fixed, and improved over time. This section introduces the techniques and habits that help you write clean, reliable, and maintainable Python code.

By the end of this section, you will be able to:

  • Use debugging techniques to find and fix errors
  • Understand and use logging to monitor programs
  • Follow established Python coding conventions
  • Write code that others (and future you) can easily understand

Lesson 1: Debugging Basics

Syntax errors and runtime errors are part of programming. Here are some ways to track them down:

  • Add temporary print() statements to trace variable values
  • Use Python's built-in error messages and traceback info
  • Check indentation, missing colons, and wrong variable names

Example:

def divide(a, b):
    print("Dividing:", a, b)
    return a / b

print(divide(10, 0))  # Will raise ZeroDivisionError 

Lesson 2: Using assert to Catch Bugs Early

An assert is a quick check you can insert to catch logic errors during development:

def calculate_discount(price, discount):
    assert discount < 1.0, "Discount must be less than 1"
    return price * (1 - discount)

Assertions stop the program if a condition is false. They're not used in production code, but they’re helpful while building and testing.


Lesson 3: Introduction to Logging

Logging gives you better control and insight than print() — especially in larger applications.

import logging

logging.basicConfig(level=logging.INFO)
logging.info("Starting program")

Log levels (from lowest to highest severity):

  • DEBUG
  • INFO
  • WARNING
  • ERROR
  • CRITICAL

Example:

import logging
logging.basicConfig(level=logging.DEBUG)

def divide(x, y):
    logging.debug(f"Trying to divide {x} by {y}")
    try:
        result = x / y
    except ZeroDivisionError:
        logging.error("Cannot divide by zero")
        return None
    return result

Lesson 4: Writing Clean Code (Best Practices)

Follow Python’s official style guide: PEP 8

Key habits:

  • Use meaningful variable names (total_price instead of tp)
  • Keep functions short and focused
  • Use comments to explain why, not what
  • Follow consistent indentation (4 spaces per level)
  • Group related functions in modules

Bad:

def d(x, y): return x/y

Better:

def divide(x, y):
    """Returns the result of x divided by y."""
    return x / y

Lesson 5: Handling Errors Gracefully with Try/Except

Don't let a single error crash your whole program. Use try/except blocks:

try:
    age = int(input("Enter your age: "))
    print(f"In 10 years you will be {age + 10}")
except ValueError:
    print("Please enter a valid number.")

You can also use finally to run cleanup code:

try:
    f = open("file.txt")
    # work with file finally:
    f.close()

Quiz: Check Your Understanding

1. What's the difference between print() and logging?
Answer: Logging provides different levels of detail and can be turned on/off or saved to files.

2. When should you use assert?
Answer: During development to catch programming errors early.

3. What does PEP 8 refer to?
Answer: The official Python style guide.

4. What log level would you use to record an unexpected error?
Answer: ERROR


Practice Exercise: Write Clean, Logged Code

Write a function safe_divide(x, y) that:

  1. Logs each attempt to divide
  2. Catches and logs division-by-zero errors
  3. Returns None if the division fails

Add an assert to check that both arguments are numbers.