Debugging and handling errors effectively is a must-have skill in Python. Here’s a complete, practical guide for:


🛠️ 1. Basic Python Error Handling Syntax

try:
    # risky code here
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"Caught error: {e}")
else:
    print("No error occurred.")
finally:
    print("Always runs, even if there’s an error.")

🔁 2. Common Python Errors and Fixes

ErrorCauseFix Example
ZeroDivisionErrorDivision by zeroCheck denominator before division
TypeErrorWrong data typeUse type() checks or cast explicitly
NameErrorVariable not definedEnsure variable is declared
KeyErrorMissing key in dictUse .get() or in check
IndexErrorList index out of rangeCheck len(list) before accessing
AttributeErrorMethod/attribute doesn’t existUse dir(obj) or check type before calling
FileNotFoundErrorFile doesn’t existUse os.path.exists() before reading
ValueErrorBad value (e.g., int("abc"))Validate input before conversion
ImportError/ModuleNotFoundMissing importEnsure module is installed and spelled correctly

🔍 3. Debugging Tips

🔸 Use print() or better: logging

import logging

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

# Or
print("Debug here: x =", x)

🔸 Use pdb – Python Debugger

python -m pdb script.py

Inside the debugger:

  • l: list code
  • n: next line
  • c: continue
  • p x: print x
  • q: quit

Or in code:

import pdb; pdb.set_trace()

🔄 4. Catch Multiple Errors

try:
    # risky stuff
except (TypeError, ValueError) as e:
    print(f"Caught: {e}")

🧹 5. Create Custom Exceptions

class InvalidAgeError(Exception):
    pass

def check_age(age):
    if age < 0:
        raise InvalidAgeError("Age cannot be negative.")

📂 6. Log Errors with Tracebacks

import traceback

try:
    1 / 0
except Exception as e:
    print("Something went wrong:")
    traceback.print_exc()

📊 7. Best Practices Summary

✅ Do This❌ Avoid This
Use try/except/finally blocksSwallowing errors silently
Log exceptions using logging moduleUsing only print() for debugging
Use traceback.print_exc() in handlersIgnoring stack traces
Create custom exceptions when neededRaising generic Exception always
Use pdb or IDE debuggers (VSCode, PyCharm)Blind debugging without breakpoints
Validate user input before parsingTrusting all input blindly

🧪 Example: Real-World Debug Wrapper

from functools import wraps
import traceback
import logging

def safe_execute(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            logging.error(f"Error in {func.__name__}: {e}")
            traceback.print_exc()
            return None
    return wrapper

@safe_execute
def risky_code():
    return 1 / 0

risky_code()

this safe_execute function is a Python decorator that wraps any function to automatically catch, log, and print any exceptions, instead of letting them crash your program.

Let’s break it down line by line, with all relevant Python concepts explained:


🧱 Code Recap

from functools import wraps
import traceback
import logging

def safe_execute(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            logging.error(f"Error in {func.__name__}: {e}")
            traceback.print_exc()
            return None
    return wrapper

🔍 Line-by-Line Explanation


def safe_execute(func):

This is a decorator function — it takes a function func as input and returns a new function (wrapper) that adds extra behavior.

Think of decorators as functions that “wrap” other functions to enhance them without modifying their original code.


@wraps(func)

Imported from functools.

This preserves metadata like the function name and docstring when it’s decorated.

Without @wraps(func), the decorated function’s name would become "wrapper" — which breaks things like introspection and debugging.


def wrapper(*args, **kwargs):

This is the inner function (the “wrapper”) that will replace the original function.

  • *args = all positional arguments
  • **kwargs = all keyword arguments

This ensures it can wrap any function, no matter what parameters it takes.


try: return func(*args, **kwargs)

The original function is executed here with the same arguments.

If it works fine, it returns the result as-is.


except Exception as e:

This catches all exceptions, not just specific ones.

Exception is the base class for most built-in errors.


logging.error(f"Error in {func.__name__}: {e}")

Logs the error using Python’s logging module instead of just printing it.

  • func.__name__ = original function’s name
  • e = actual error message

traceback.print_exc()

Prints the full stack trace, like what you’d see if the program crashed — very helpful for debugging.


return None

If an error occurs, the wrapper fails gracefully by returning None.

You could change this to return an empty DataFrame, False, or even re-raise the error, depending on your use case.


return wrapper

The decorator finally returns the enhanced wrapper function — which now has error handling built in.


🚀 How to Use It

@safe_execute
def divide(a, b):
    return a / b

print(divide(10, 2))  # ✅ Works fine: prints 5.0
print(divide(10, 0))  # ❌ Logs error + traceback, returns None

🧠 What This Does for You

FeatureBenefit
@wrapsKeeps function name/docs intact
*args, **kwargsMakes the decorator generic
try/exceptPrevents crash, adds graceful error handling
logging + tracebackLogs full error trace for debugging
return NoneFails gracefully; doesn’t raise unhandled exception

Pages: 1 2


Discover more from HintsToday

Subscribe to get the latest posts sent to your email.

Posted in

Leave a Reply

Discover more from HintsToday

Subscribe now to keep reading and get access to the full archive.

Continue reading