← Home

078. Try/Except

Handle expected errors gracefully instead of crashing

078. Try/Except

He reads /proc/*/status to get RSS for each process. Some entries vanish mid-read (the process exited). Some fields are not integers. Without error handling, one bad line crashes the whole collection loop.

# Dangerous: crashes on any bad value
def parse_rss_kb(line):
    return int(line.split()[1])

# Safe: returns None for unparseable lines
def parse_rss_kb(line):
    try:
        return int(line.split()[1])
    except (ValueError, IndexError):
        return None

The safe version collects stats for every healthy process and skips bad entries β€” exactly what a production monitoring tool does.

πŸ’‘ Design principle: Functions that can legitimately fail (parsing, I/O, network) should use try/except and return a sentinel value (None, [], {}) so the caller can continue working. Functions that should never fail (logic bugs) should let exceptions propagate.

Learning objectives

  • Wrap risky code in try/except blocks
  • Catch specific exception types
  • Return sensible values when exceptions occur

Key concepts

  • try
  • except
  • exception handling
  • ValueError
  • KeyError

Try it

Concept detail

try/except catches exceptions so the program continues instead of crashing.

try:
    result = risky_operation()
except SomeError:
    result = fallback_value

Execution flow:

  1. Python runs the code inside try
  2. If no exception β†’ skips the except block, continues normally
  3. If the specified exception is raised β†’ jumps to except block
  4. After except β†’ execution continues as normal

Be specific β€” always name the exception type: except ValueError: # catches value errors only except (ValueError, TypeError): # catches either

NEVER do bare β€œexcept:” β€” it catches KeyboardInterrupt and SystemExit, making your program impossible to Ctrl-C out of.

Access the exception object: except ValueError as e: print(fβ€œBad value: {e}β€œ) # shows the error message

WHY catch exceptions instead of pre-checking: # Pre-check (LBYL β€” Look Before You Leap): if key in d: value = d[key] # try/except (EAFP β€” Easier to Ask Forgiveness than Permission): try: value = d[key] except KeyError: value = default

EAFP is the Pythonic style β€” it’s faster when errors are rare and handles race conditions that pre-checks can’t.

Solution

def safe_int(s):
    try:
        return int(s)
    except ValueError:
        return None

def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None

def safe_get(d, key, default=None):
    try:
        return d[key]
    except KeyError:
        return default

Tests

def test_safe_int_valid():
    assert safe_int("42") == 42
    assert safe_int("-7") == -7
    assert safe_int("0") == 0

def test_safe_int_invalid():
    assert safe_int("abc") is None
    assert safe_int("3.14") is None
    assert safe_int("") is None

def test_safe_divide_normal():
    assert safe_divide(10, 2) == 5.0
    assert safe_divide(7, 2) == 3.5

def test_safe_divide_zero():
    assert safe_divide(5, 0) is None
    assert safe_divide(0, 0) is None

def test_safe_get_exists():
    d = {"a": 1, "b": 2}
    assert safe_get(d, "a") == 1
    assert safe_get(d, "b") == 2

def test_safe_get_missing():
    d = {"a": 1}
    assert safe_get(d, "z") is None
    assert safe_get(d, "z", 99) == 99
    assert safe_get(d, "z", "fallback") == "fallback"

Resources