Exception Handling

15 questions found

How would you implement a retry-with-backoff decorator that catches specific transient exceptions and retries with increasing delays?

Advanced
Wrap the function call in a loop with a try/except catching only the SPECIFIC transient exception types you expect (like a network timeout), sleeping with an exponentially increasing delay between attempts, and re-raising the final exception if all attempts are exhausted.
import time, functools

def retry(exceptions, times=3, delay=1):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(times):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == times - 1:
                        raise
                    time.sleep(delay * (2 ** attempt))
            return wrapper
    return decorator

@retry((ConnectionError, TimeoutError), times=3)
def fetch_data():
    ...
Real-world example Retrying a flaky network request up to three times with increasing delays before giving up and propagating the final failure.

Common follow-ups: Why is it important to only catch SPECIFIC transient exception types here, rather than a bare 'except Exception'?

Decorators

How does exception performance (raising/catching) compare to normal control flow, and why does Python's EAFP style still encourage using exceptions for routine control flow, unlike some other languages?

Advanced
Raising an exception involves stack unwinding and traceback construction, making it measurably slower than a simple conditional check — HOWEVER, Python's 'Easier to Ask Forgiveness than Permission' (EAFP) philosophy still favors try/except for many cases (like dict key lookups) because the COMMON case (no exception) is actually FASTER than a defensive check-first approach, and exceptions only cost extra time on the (hopefully rare) failure path.
# EAFP: fast in the common case, since 'in' checks aren't needed
try:
    value = my_dict["key"]
except KeyError:
    value = "default"

# LBYL (Look Before You Leap): always pays the cost of the membership check
if "key" in my_dict:
    value = my_dict["key"]
else:
    value = "default"
Real-world example Understanding why Python code idiomatically favors try/except over defensive pre-checks in many common scenarios, unlike Java or C#.

Common follow-ups: In what scenario would the EAFP approach actually be SLOWER than LBYL, due to exceptions being genuinely frequent rather than rare?

Debugging & Profiling

How would you write a context manager using @contextlib.contextmanager that both sets up a resource AND properly handles/logs any exception that occurs while it's in use?

Advanced
Wrap the yield in a try/except/finally: the try/yield gives control to the 'with' block's body, an except clause can inspect and optionally re-raise (or transform) any exception that occurred inside that body, and finally handles guaranteed cleanup regardless of the outcome.
from contextlib import contextmanager
import logging

@contextmanager
def managed_transaction(db):
    transaction = db.begin()
    try:
        yield transaction
        transaction.commit()
    except Exception:
        transaction.rollback()
        logging.exception("Transaction failed, rolled back")
        raise
    finally:
        transaction.close()
Real-world example Building a database transaction context manager that automatically commits on success, rolls back and logs on failure, and always cleans up.

Common follow-ups: Why does the 'except' block here re-raise the exception after rolling back, rather than swallowing it?

File I/O & Context Managers

How would you implement a circuit breaker pattern using exception handling, to stop repeatedly calling a service that's consistently failing?

Advanced
Track consecutive failure counts; once a threshold is exceeded, the circuit 'opens' and immediately raises a CircuitOpenError WITHOUT even attempting the real call for a cooldown period, protecting the failing service from being hammered with more requests — after the cooldown, allow a single 'trial' call through to test if the service has recovered.
class CircuitBreaker:
    def __init__(self, threshold=3, cooldown=30):
        self.failures = 0
        self.threshold = threshold
        self.cooldown = cooldown
        self.opened_at = None
    def call(self, func, *args):
        if self.opened_at and time.time() - self.opened_at < self.cooldown:
            raise RuntimeError("Circuit is open")
        try:
            result = func(*args)
            self.failures = 0
            self.opened_at = None
            return result
        except Exception:
            self.failures += 1
            if self.failures >= self.threshold:
                self.opened_at = time.time()
            raise
Real-world example Preventing a struggling downstream service (like a flaky third-party API) from being overwhelmed with continued retry attempts during an outage.

Common follow-ups: How would you extend this circuit breaker to support a 'half-open' state that allows a limited number of trial requests through?

Concurrency (asyncio/threading/multiprocessing)

Why is a bare 'except:' clause (catching everything with no type specified) generally discouraged?

Beginner
A bare 'except:' catches literally everything, including SystemExit and KeyboardInterrupt (which are meant to actually terminate the program), silently swallowing bugs like typos (NameError) or programming mistakes that should surface loudly rather than being hidden — 'except Exception:' is a safer, still-broad alternative that excludes those system-level exceptions.
# Risky: catches EVERYTHING, including Ctrl+C and typos
try:
    resutl = compute()  # typo -- NameError
except:
    pass  # silently swallows the typo bug, and even Ctrl+C!

# Safer: still broad, but excludes SystemExit/KeyboardInterrupt
try:
    result = compute()
except Exception as e:
    logging.error(f"Unexpected error: {e}")
Real-world example Debugging a program that couldn't be interrupted with Ctrl+C because a bare except: clause was silently swallowing the KeyboardInterrupt.

Common follow-ups: What specific exception classes does 'except Exception' still fail to catch, that only a bare 'except:' would?

Debugging & Profiling

Showing 11–15 of 15