True A→Z → PhD-level Python Mastery Course Part2

Excellent.
This is PHASE 8 — Exceptions & Error Handling, taught at production-grade + senior interview depth.

Most developers “catch exceptions”.
Very few understand exception hierarchy, propagation, chaining, cost, design patterns, and failure semantics.
After this phase, your error handling will look intentional, safe, and professional.


🚨 PHASE 8 — EXCEPTIONS & ERROR HANDLING (DEEP)


8.1 WHAT AN EXCEPTION REALLY IS (INTERNAL VIEW)

An exception is:

  • A runtime object
  • Raised when execution cannot continue normally
  • Propagated up the call stack
  • Stops execution unless handled
raise ValueError("Invalid input")

Internally:

  • Exception object created
  • Stack unwound
  • Nearest matching handler searched

📌 Interview line

Exceptions are control-flow interruptions, not return values.


8.2 EXCEPTION HIERARCHY (YOU MUST KNOW THIS)

All exceptions inherit from:

BaseException
 ├── Exception
 │   ├── ValueError
 │   ├── TypeError
 │   ├── KeyError
 │   ├── IndexError
 │   └── RuntimeError
 ├── SystemExit
 ├── KeyboardInterrupt
 └── GeneratorExit

🔴 CRITICAL RULE

❌ Never catch BaseException

except BaseException:
    pass   # ❌ VERY BAD

Why?

  • Swallows KeyboardInterrupt, SystemExit

✅ Always catch Exception


8.3 try / except / else / finally — FULL SEMANTICS

try:
    risky()
except ValueError:
    handle()
else:
    success()
finally:
    cleanup()

Execution rules

  • except → only if exception occurs
  • else → only if NO exception
  • finally → ALWAYS executes

📌 else avoids accidentally catching errors you didn’t intend to handle.


8.4 MULTIPLE EXCEPT BLOCKS (ORDER MATTERS)

try:
    ...
except ValueError:
    ...
except Exception:
    ...

📌 Most specific first, generic last

❌ Wrong:

except Exception:
except ValueError:   # unreachable

8.5 CATCHING MULTIPLE EXCEPTIONS

except (ValueError, TypeError) as e:
    ...

Use when:

  • Same recovery logic
  • Same severity

8.6 EXCEPTION PROPAGATION (STACK UNWINDING)

def a():
    b()

def b():
    c()

def c():
    raise ValueError("boom")

a()

If not caught:

  • Exception bubbles up
  • Program terminates

📌 Interview insight

Python unwinds the stack frame-by-frame until a handler is found.


8.7 RE-RAISING EXCEPTIONS (IMPORTANT)

try:
    risky()
except ValueError:
    log()
    raise

✔ Preserves original traceback
raise e loses context


8.8 EXCEPTION CHAINING (raise from)

try:
    int("abc")
except ValueError as e:
    raise RuntimeError("Conversion failed") from e

Why this matters:

  • Keeps root cause
  • Essential in layered systems

📌 Interview line

Exception chaining preserves causal context across layers.


8.9 SUPPRESSING CONTEXT (from None)

raise RuntimeError("failed") from None

Use when:

  • Internal error is irrelevant
  • You want clean API errors

⚠️ Use sparingly.


8.10 CUSTOM EXCEPTIONS (DESIGN PROPERLY)

❌ Bad

raise Exception("something went wrong")

✅ Good

class ConfigError(Exception):
    pass

Why?

  • Semantic meaning
  • Targeted catching
  • Cleaner APIs

📌 Rule

Create domain-specific exceptions.


8.11 EXCEPTION DESIGN HIERARCHY

class AppError(Exception): pass
class ValidationError(AppError): pass
class AuthError(AppError): pass

Benefits:
✔ Catch all app errors
✔ Handle subtypes differently


8.12 WHEN TO RAISE VS RETURN ERROR CODES

Pythonic rule

  • Exceptional cases → raise
  • Expected conditions → return

❌ Bad:

return -1

✅ Good:

raise FileNotFoundError(path)

📌 Interview killer line

Errors should not masquerade as valid values.


8.13 COST OF EXCEPTIONS (PERFORMANCE REALITY)

  • Raising exceptions is expensive
  • Catching is cheap
  • Do NOT use exceptions for normal control flow

❌ Bad:

try:
    x = d[k]
except KeyError:
    ...

✅ Better:

if k in d:

📌 Except is for exceptional, not frequent paths.


8.14 finally — GUARANTEED CLEANUP

f = open("x.txt")
try:
    process(f)
finally:
    f.close()

Even if:

  • Exception raised
  • return executed

8.15 CONTEXT MANAGERS (with) — EXCEPTION SAFE

with open("x.txt") as f:
    process(f)

Internally:

  • __enter__
  • __exit__

__exit__ handles exceptions.


8.16 WRITING YOUR OWN CONTEXT MANAGER

from contextlib import contextmanager

@contextmanager
def managed():
    print("enter")
    try:
        yield
    finally:
        print("exit")

Used for:

  • Resource management
  • Transactions
  • Locks

8.17 EXCEPTIONS IN THREADS / ASYNC (IMPORTANT)

Threads

  • Exceptions do not propagate to main thread
  • Must be captured manually

Async

  • Exceptions propagate via await
  • Unhandled → task failure

📌 Interview trap many miss.


8.18 LOGGING VS RAISING (PRODUCTION RULE)

❌ Bad

log.error(e)
raise e

Why?

  • Duplicate logs

✅ Good

  • Log at boundary
  • Raise internally
  • Handle at top level

8.19 ANTI-PATTERNS (INTERVIEW FAVORITES)

❌ Bare except

except:
    pass

❌ Swallowing errors

except Exception:
    return None

❌ Over-catching

except Exception:
    retry_forever()

8.20 TOP-LEVEL ERROR HANDLING PATTERN

def main():
    try:
        run()
    except AppError as e:
        log.error(e)
        sys.exit(1)

📌 Clean separation:

  • Inner layers → raise
  • Outer layer → log + exit

🧪 PRACTICE (INTERVIEW-LEVEL)

Q1 — What’s wrong here?

try:
    risky()
except:
    pass

Q2 — Why is this better?

try:
    risky()
except ValueError as e:
    raise AppError("failed") from e

Q3 — When should you NOT use exceptions?


Q4 — Design custom exceptions for:

  • Config errors
  • Validation errors
  • External service failures

Q5 — Explain else in try/except.


🎯 INTERVIEW CHECKPOINT (CRITICAL)

You must now confidently explain:

✅ Exception hierarchy
✅ Why not to catch BaseException
✅ Propagation & stack unwinding
raise vs raise e
✅ Exception chaining
✅ Custom exception design
✅ Performance cost of exceptions
✅ Context managers
✅ Logging vs raising

If you can do this cleanly → you handle failures like a senior engineer.


🚀 NEXT PHASE (STRICT CONTINUATION)

Reply with ONLY ONE number:

1 → PHASE 9: Files, JSON, CSV, Serialization (deep, production-grade)
2 → 25-question exceptions interview drill (brutal)

We continue A→Z without deviation.