Skip to main content

← All cheatsheets · Track →

🧠Updated 2026-05

Senior Python cheatsheet — async internals, GIL, perf, descriptors

Senior-level depth from the Senior Deep-Dives track. Stuff that separates a mid from a senior in code review and architecture discussion.

Async internals

Coroutine = generator + protocol

Under the hood, `async def` returns a coroutine object; `await` is `yield from` with extra rules.

import asyncio
async def f():
    return 42
asyncio.iscoroutinefunction(f)  # True

Event loop

Single-threaded scheduler. CPU-bound work blocks it.

# Bad: time.sleep(5) in async def — freezes the loop
# Good: await asyncio.sleep(5)

gather vs wait

`gather` returns results; `wait` returns (done, pending) sets — more control.

results = await asyncio.gather(a(), b(), c())
# vs
done, pending = await asyncio.wait([t1, t2], return_when=asyncio.FIRST_COMPLETED)

TaskGroup (3.11+)

Modern replacement for `gather` — structured concurrency, exceptions handled cleanly.

async with asyncio.TaskGroup() as tg:
    tg.create_task(work_a())
    tg.create_task(work_b())

GIL + concurrency

GIL purpose

Protects CPython internals — only one thread runs Python bytecode at a time.

# I/O-bound: threads still help (release GIL during syscalls)
# CPU-bound: use multiprocessing or 3.13+ free-threading

Thread for I/O

Cheap, shared memory, fine for parallel HTTP calls.

from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(8) as ex:
    results = list(ex.map(fetch, urls))

Process for CPU

Bypasses GIL — each process has its own interpreter.

from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor(4) as ex:
    results = list(ex.map(heavy_compute, chunks))

Free-threading (3.13+)

Experimental no-GIL build. Genuine threaded parallelism for CPU work.

python3.13t -X gil=0 script.py

Descriptors + metaclasses

Descriptor protocol

Powers `@property`, `staticmethod`, ORM fields. `__get__`/`__set__`/`__delete__`.

class Validated:
    def __set_name__(self, owner, name): self.name = name
    def __get__(self, inst, owner): return inst.__dict__[self.name]
    def __set__(self, inst, value):
        if value < 0: raise ValueError
        inst.__dict__[self.name] = value

Metaclass = class of a class

Use sparingly. Most cases solved by `__init_subclass__`.

class Tracked:
    _all = []
    def __init_subclass__(cls, **kw):
        super().__init_subclass__(**kw)
        Tracked._all.append(cls)

__slots__

Save memory by skipping __dict__ per instance.

class Point:
    __slots__ = ("x", "y")
    def __init__(self, x, y): self.x, self.y = x, y

Perf

cProfile

Function-level timings — find the hot path.

python -m cProfile -o out.pstats script.py
python -m pstats out.pstats <<< "sort cumulative\nstats 20"

memray

Heap profiler — find leaks and giant allocations.

memray run script.py
memray flamegraph memray-script.bin

functools.cache

Replace hand-rolled dict caches.

from functools import cache
@cache
def fib(n): return n if n < 2 else fib(n-1) + fib(n-2)

Vectorise with numpy

C loop > Python loop for numeric work.

np.where(arr > 0, arr * 2, 0)  # vs a Python for loop

Architecture review

ADR template

Capture the why of every load-bearing decision.

# ADR-0042: Use Redis Streams over Kafka
# Status: accepted
# Context: …
# Decision: …
# Consequences: + / -

Code review heuristics

What a senior actually checks.

# 1. Does it have to exist?
# 2. Is the failure mode obvious?
# 3. Will it page someone at 3am?
# 4. What's the test that proves it works?

Want to actually learn these patterns, not just paste them? Open the Senior Python cheatsheet track — each snippet has a full lesson behind it.