This sheet pins to Python 3.11+. match /
TaskGroup / tomllib /
PEP 604 union syntax (int | None) are assumed. For 3.12+ also
type X = ... aliases (PEP 695). Drop a version target lower
only if you have to.
Run a sync function in a thread without blocking the loop.
python
import asyncio
import httpx
async def fetch(client: httpx.AsyncClient, url: str) -> int:
r = await client.get(url, timeout=5.0)
return r.status_code
async def main(urls: list[str]) -> list[int]:
async with httpx.AsyncClient() as client:
# asyncio.TaskGroup (3.11+) — structured concurrency
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch(client, u)) for u in urls]
return [t.result() for t in tasks]
# Run from a sync entry point
results = asyncio.run(main(["https://example.com", "https://example.org"]))
# Bound concurrency with a Semaphore
sem = asyncio.Semaphore(8)
async def bounded(client, url):
async with sem:
return await fetch(client, url)
paths as objectsPathlib
Path("a/b") / "c.txt"
Compose with /.
p.exists(), p.is_file(), p.is_dir()
Predicates.
p.read_text(encoding="utf-8")
Whole-file read.
p.write_text(s, encoding="utf-8")
Whole-file write.
p.write_bytes(b)
Binary write.
p.mkdir(parents=True, exist_ok=True)
Idempotent directory creation.
p.glob("*.json") / p.rglob("*.json")
Top-level / recursive globs.
p.stem, p.suffix, p.name, p.parent
Path parts.
p.with_suffix(".csv")
Swap extensions.
p.relative_to(root)
Make a path relative.
p.resolve()
Absolute, with symlinks resolved.
python
from pathlib import Path
p = Path("data/inputs/log.txt")
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("hello\n", encoding="utf-8")
content = p.read_text(encoding="utf-8")
# Glob / iterate
for f in Path("data").rglob("*.json"):
print(f.relative_to("data"))
# Compose paths
out = Path.home() / "exports" / f"{p.stem}.csv"
# Inspect
p.exists(), p.is_file(), p.suffix, p.stem, p.name
# Streaming line read
with p.open(encoding="utf-8") as fh:
for line in fh:
...
combinators · cachesItertools & functools
itertools
chain(a, b)
Concatenate iterables lazily.
chain.from_iterable(iters)
Flatten one level.
pairwise(seq)
Sliding window of 2. (3.10+)
groupby(seq, key=…)
Consecutive-key grouping. Sort first if needed.
accumulate(seq, fn)
Running totals / scans.
batched(seq, n)
Yield N-sized chunks (3.12+).
islice(seq, start, stop)
Slice an iterator without materialising.
product / combinations / permutations
Combinatorics.
functools
@cache
Unbounded memoisation. Pure functions only.
@lru_cache(maxsize=1024)
Bounded memoisation. Cheap to add, easy to undo.
@cached_property
Lazy attribute, computed once per instance.
partial(fn, x=1)
Pre-bind args. Useful for callbacks.
reduce(fn, seq, init)
Fold. Most loops are clearer.
@singledispatch
Function overloads by first-arg type.
resource handlingContext managers
with open(p) as fh:
Most common cm. Closes on exit.
with suppress(FileNotFoundError):
Quietly swallow specific exceptions.
@contextmanager
Define a cm from a generator. Yield in the middle.
@asynccontextmanager
Async version.
ExitStack()
Dynamic stack of context managers. Closes in reverse order.
redirect_stdout(io.StringIO())
Capture stdout in tests.
tempfile.TemporaryDirectory()
Scratch dir auto-cleaned on exit.
python
from contextlib import contextmanager, asynccontextmanager, ExitStack
import time
@contextmanager
def timed(label: str):
t0 = time.perf_counter()
try:
yield
finally:
print(f"{label}: {time.perf_counter() - t0:.3f}s")
with timed("query"):
result = run_query()
# Open many files; close them all even if one open() raises
with ExitStack() as stack:
files = [stack.enter_context(open(p)) for p in paths]
...
# Async variant — perfect for httpx / DB sessions
@asynccontextmanager
async def session():
s = await open_session()
try:
yield s
finally:
await s.close()
wrap behaviourDecorators
@functools.wraps(fn)
Always use it in your own decorators. Preserves __name__ + docstring.
@staticmethod / @classmethod
Bound or not. classmethod takes cls.
@property
Computed attribute. Pair with @x.setter if writable.
@cache · @lru_cache
Memoise. Pure functions only.
@contextmanager
Make a generator into a with context.
@dataclass
Auto-generates dunders.
@override (3.12+)
Mark a method as overriding a base. Type checker enforces.
A decorator with arguments is a decorator that returns a decorator. Easy mistake: forgetting the
extra wrapper layer. Use functools.wraps inside the innermost function.
exceptions & groupsErrors
raise Foo("msg") from exc
Chain causes. from None to hide.
try / except / else / finally
else runs only if no exception. Useful for tight try blocks.
except (A, B) as e:
Catch multiple types.
except* GroupError:
Match members of an ExceptionGroup (3.11+).
raise ExceptionGroup("all failed", [a, b])
Aggregate concurrent failures.
assert cond, "why"
Debug check. Stripped under python -O; never use for validation.
contextlib.suppress(KeyError)
Compact alternative to try/except/pass.
structural pattern matchingMatch
case 200:
Literal match.
case [head, *tail]:
Sequence with capture.
case {"type": "text", "value": v}:
Mapping match.
case User(id=int() as uid, name=str() as name):
Class match with type guards.
case _ if x > 0:
Guard clause.
case _:
Wildcard. Always include unless every case is exhaustive.
Reach for match on shape, not on equality. If you’d write
elif x == 1 / x == 2 / …, a dict dispatch is clearer. match
shines when you’re destructuring nested data.
Fetch repos in parallel, validate with Pydantic, write a CSV. Wires async + structured concurrency +
pathlib + csv in one runnable script.
python
# Fetch JSON from N endpoints in parallel, validate, write a CSV summary.
import asyncio, csv
from pathlib import Path
import httpx
from pydantic import BaseModel, HttpUrl, ValidationError
class Repo(BaseModel):
full_name: str
stargazers_count: int
html_url: HttpUrl
async def fetch_repo(client: httpx.AsyncClient, name: str) -> Repo | None:
r = await client.get(f"https://api.github.com/repos/{name}", timeout=10)
if r.status_code != 200:
return None
try:
return Repo.model_validate(r.json())
except ValidationError:
return None
async def main(repos: list[str], out: Path) -> None:
async with httpx.AsyncClient(headers={"Accept": "application/vnd.github+json"}) as c:
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch_repo(c, r)) for r in repos]
rows = [t.result() for t in tasks if t.result()]
out.parent.mkdir(parents=True, exist_ok=True)
with out.open("w", newline="", encoding="utf-8") as fh:
w = csv.writer(fh)
w.writerow(["repo", "stars", "url"])
for r in rows:
w.writerow([r.full_name, r.stargazers_count, str(r.html_url)])
if __name__ == "__main__":
asyncio.run(main(
["python/cpython", "django/django", "pallets/flask"],
Path("out/repos.csv"),
))
Best practiceGood to know
Prefer TaskGroup over gather.
Structured concurrency cancels siblings on failure and surfaces all errors as an
ExceptionGroup. gather swallows them silently
unless you remember return_exceptions=False.
Use slots=True on hot-path dataclasses.
Big memory + attribute-access win, almost no downside. Skip it only if you need to monkey-patch instances.
Run modules with python -m.python -m pkg.cli sets sys.path correctly and
respects relative imports; calling pkg/cli.py directly often doesn’t.
Common trapsWatch out for
Mutable default arguments are evaluated once.def f(x=[]): shares the list across calls. Use
x: list | None = None and x = x or [] inside.
Late-binding closures.[lambda: i for i in range(3)] all print 2.
Capture with a default: lambda i=i: i.
Don’t mix asyncio with blocking calls.
Calling sync requests.get inside an async handler stalls the event loop.
Use httpx/aiohttp, or wrap with
asyncio.to_thread.
Comprehensions are compact expressions for building lists, dicts, sets, and generators. A list comprehension [x*2 for x in range(10) if x%2==0] replaces a for loop with an append. Dict comprehensions use {k: v for k, v in items}, set comprehensions {x for x in seq}, and generator expressions (x for x in seq) are lazy. Prefer comprehensions when the transformation is simple and readable.
What are Python dataclasses?
A dataclass (@dataclass decorator from the dataclasses module) auto-generates __init__, __repr__, and __eq__ from annotated class fields. Add frozen=True for immutable instances, slots=True (3.10+) for memory efficiency, or order=True to generate comparison methods. They are the modern alternative to named tuples and plain dicts for structured data.
How does async/await work in Python?
async def defines a coroutine; await suspends it until the awaited object completes, yielding control back to the event loop. asyncio.run(main()) starts the loop. Use asyncio.gather() to run coroutines concurrently, asyncio.create_task() to schedule without blocking, and async for / async with for async iterators and context managers.
What is the Python type system?
Python's type system is optional and enforced by external tools (mypy, Pyright, Ruff), not the runtime. Annotate variables and function signatures with types like str, list[int], dict[str, Any], Optional[X] (or X | None in 3.10+), and Union. Generic types use TypeVar; TypedDict annotates dict shapes. PEP 695 (3.12+) adds type X = ... aliases.
What is pathlib and when should I use it?
pathlib.Path is the modern API for filesystem operations — it replaces os.path, open(), and os calls with a readable object-oriented interface. Path('data') / 'file.txt' builds paths; path.read_text(), path.write_bytes(), path.glob('*.csv') cover most I/O needs. Use pathlib for all new code; os.path still works but is less readable.
Is Python free and open source?
Yes. Python is governed by the PSF (Python Software Foundation) under the Python Software Foundation License, which is OSI-approved and free for commercial use. CPython (the reference implementation) is free to download and ship. PyPI (the package index) is also free to publish to and install from.