Handbooks / Python / Chapter 4

Errors and the Standard Library

7 pages · ~5 min✓ Reviewed

Builds on OOP in Python. Next up: Iterators and Generators.

PythonPage 1

Exception Handling Mental Model

1. Raise, Unwind, Run Order

  • An exception is an object raised at runtime
  • It unwinds the call stack to the first matching except
  • Uncaught: the program ends with a traceback
  • finally runs even after return, break or an uncaught error
  1. tryrun body
  2. exceptonly if raised
  3. elseonly if no error
  4. finallyalways
python
def f():
    try:
        return "try"
    finally:
        print("finally")
print(f())

finally runs before the return value is delivered

output
finally
try

2. Matching Clauses

  • Clauses checked top to bottom: first match wins
  • List specific subclasses before parents like Exception
  • Tuple catches several: except (KeyError, IndexError) as e
  • e is deleted when the except block ends

3. Catching Too Much

ClauseSwallows
except: / except BaseExceptionCtrl+C, SystemExit
except ExceptionBugs, but not Ctrl+C
except KeyErrorOnly that type
Default

Catch a specific type by default, Exception at most. Never a bare except:.

PythonPage 2

Raising and Custom Exceptions

1. Raising and Chaining

  • raise ValueError('msg') raises a new exception
  • Bare raise inside except re-raises the current one, traceback intact
FormSetsTraceback
raise B from A__cause__ = Adirect cause
raise B in except__context__, automaticduring handling
raise B from None__cause__ None, __suppress_context__ Truechain hidden, __context__ kept

2. Custom Exceptions

  • class AppError(Exception): pass
  • Subclass Exception, never BaseException
  • One base class per library, subclass it per case
  • Store fields in __init__, call super().__init__(msg)
  • Callers read e.args and the attributes
python
class AppError(Exception): pass
class DbError(AppError):
    def __init__(self, msg, code):
        super().__init__(msg)
        self.code = code
try: raise DbError('down', 503)
except AppError as e: print(e.args, e.code)
output
('down',) 503

3. Mistakes

Never swallow errors

except Exception: pass hides bugs. Raising a bare string is a TypeError. Print-and-continue loses the failure: log or re-raise.

PythonPage 3

Collections: Counter, defaultdict, deque

1. Counter

  • Counter(iterable) counts hashables
  • .most_common(n) gives the top n (item, count) pairs
  • A missing key gives 0, not KeyError
  • + and - combine counters; - drops counts of 0 or less
python
from collections import Counter
c = Counter('banana')
print(c.most_common(2), c['z'])
print(c + Counter('an'))
output
[('a', 3), ('n', 2)] 0
Counter({'a': 4, 'n': 3, 'b': 1})

2. defaultdict

  • defaultdict(list) calls the factory to build a missing value on first access
  • d[k].append(x) needs no key check
  • Factory: int, list, set or lambda: 'n/a'
  • Reading a missing key inserts it
  • Test safely with d.get(k) or k in d

3. deque

  • deque(maxlen=n): double-ended queue, O(1) append, appendleft, pop, popleft
  • A full maxlen deque drops items from the opposite end
Operationlistdeque
Remove from frontpop(0): O(n)popleft(): O(1)
Add at frontinsert(0, x): O(n)appendleft(x): O(1)
Index in the middleO(1)O(n)
Pick one

Queue: deque. Random access by index: list.

PythonPage 4

pathlib and json

1. Paths and files

  • Path('data') / 'file.txt' joins with /
  • Path.cwd(), Path.home(); .parent .name .stem .suffix beat slicing
  • read_text(encoding='utf-8') and write_text(s): whole file
  • exists(), is_file(), glob('*.py'), mkdir(parents=True, exist_ok=True)
python
from pathlib import Path
p = Path('out') / 'log.txt'
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text('hi', encoding='utf-8')
print(p.read_text(encoding='utf-8'), p.stem, p.suffix)
output
hi log .txt

2. json

  • dumps(obj, indent=2) / loads(s): strings
  • dump(obj, f) / load(f): file objects; the s means string
PythonJSON
dictobject
list, tuplearray
None, Truenull, true
(1, 2) round triplist [1, 2]
{1: 'a'} round tripstring key '1'
Mistake

dumps raises TypeError on datetime, set, custom objects. Pass default=str or a function. Open json files with encoding='utf-8'.

PythonPage 5

Typing Basics and the GIL

1. Type Hints

  • Hints annotate only; Python never enforces them at runtime
  • mypy and pyright check them
python
def f(x: int, y: str = 'a') -> bool:
    return x > 0
FormMeaning
list[int]list of ints
dict[str, int]str keys, int values
tuple[int, ...]any length of ints
int | Noneor Optional[int]
Callable[[int], str]int in, str out
Anyescape hatch

2. The GIL

  • Default (GIL) build: one thread runs bytecode at a time
  • CPU-bound threads gain no speedup
  • Free-threaded build (python3.14t): no GIL, threads run in parallel
  • GIL is released while waiting on I/O
WorkUse
I/O-boundthreads
CPU-boundmultiprocessing, ProcessPoolExecutor

3. Common Mistake

GIL does not mean thread-safe

counter += 1 is not atomic. Guard shared state with threading.Lock.

python
with lock:
    counter += 1
PythonPage 6

Python Essentials Cheat Sheet

1. Errors

  • else runs on success, finally always
  • except order: specific before general
  • Bare raise re-raises; raise X from e chains
  • from None hides the chain, __context__ is still kept
  • class AppError(Exception); catch Exception, not BaseException
python
try:
    run()
except KeyError as e:
    raise AppError("bad") from e
else:
    ok()
finally:
    close()
Never

except: pass hides bugs. Log or re-raise.

2. Collections

ToolRemember
Countermost_common(n)
defaultdictdefaultdict(list)
dequedeque(maxlen=n), O(1) appendleft/popleft

3. pathlib and json

CallDoes
Path('a') / 'b'join paths
read_text / write_textwhole file
mkdir(parents=True, exist_ok=True)make dirs
glob('*.py')match files
dumps / loadsstrings
dump / loadfiles

4. Hints and the GIL

  • Hints are unenforced: int | None, list[int]
  • Default (GIL) build: threads for I/O, processes for CPU
  • Free-threaded build (python3.14t) has no GIL
  • counter += 1 is not atomic: guard shared state with Lock
PythonPage 7

Check yourself

What does this print?
  • fin, then try
  • finally runs before the function actually returns
def f():
    try:
        return 'try'
    finally:
        print('fin')
print(f())
After d = defaultdict(int) and a read of d['a'], what is len(d)?
  • 1: reading a missing key inserts it
  • Test safely with d.get('a') or 'a' in d
What does json.loads(json.dumps({1: (2, 3)})) return?
  • {'1': [2, 3]}
  • Dict keys become strings and tuples come back as lists