Variables & Basicscore
x = 5 # dynamic typing, no declaration
a, b = 1, 2 # multiple assignment
a, b = b, a # swap (tuple pack/unpack)
x = y = z = 0 # chained
first, *rest = [1,2,3,4] # starred unpack -> rest=[2,3,4]
PI = 3.14159 # UPPER = constant by convention
if (n := len(data)) > 10: # walrus := assigns in expression
print(f"{n} items")
type(x) #
isinstance(x, (int, float)) # True — prefer over type()==
id(x) # object identity (CPython: address)
del x # unbind name
x is None # identity check — always for None
bool([]), bool(0), bool("") # all False (falsy)
# Falsy: None, False, 0, 0.0, "", [], {}, (), set() | op | meaning |
|---|---|
| == != | value equality |
| is / is not | identity (same object) |
| and or not | short-circuit; return operand, not bool |
| in / not in | membership |
| a < b < c | chained comparison |
x or default returns default if x falsy; a and b returns b if a truthy.
Numbers & Mathcore
7 / 2 # 3.5 true division (always float)
7 // 2 # 3 floor division (rounds toward -inf!)
-7 // 2 # -4 ← beware
7 % 2 # 1 modulo (sign follows divisor)
divmod(7, 2) # (3, 1)
2 ** 10 # 1024 power
pow(2, 10, 97) # modular pow — fast
round(2.675, 2) # 2.67 — float repr + banker's rounding
round(2.5) # 2 (rounds half to even!)
abs(-3), int("ff", 16), float("inf"), float("nan")
0b1010, 0o17, 0xFF # binary/octal/hex literals
1_000_000 # underscore separators
f"{255:b} {255:o} {255:x}" # '11111111 377 ff'
# bitwise
a & b, a | b, a ^ b, ~a, a << 2, a >> 2
(n & (n-1)) == 0 # power-of-2 test
x.bit_length(), x.bit_count()
import math
math.floor(-1.5), math.ceil(1.2), math.trunc(-1.5) # -2, 2, -1
math.isclose(0.1+0.2, 0.3) # True — never == floats
math.gcd(12, 18), math.lcm(4, 6), math.sqrt(2), math.log2(8)
math.prod([1,2,3,4]), math.factorial(5), math.comb(5,2)
from decimal import Decimal # exact decimal arithmetic
Decimal("0.1") + Decimal("0.2") # Decimal('0.3')
from fractions import Fraction
Fraction(1, 3) + Fraction(1, 6) # Fraction(1, 2)2**10000 just works.Stringscore
s = 'single' + "double" + '''triple
multiline'''
r"raw\nstring" # no escape processing (regex, paths)
b"bytes" # bytes literal
"ab" * 3 # 'ababab'
s[0], s[-1] # indexing
s[2:5], s[::-1] # slice, reverse
"na" in "banana" # True
len(s), min(s), max(s), sorted(s)
str(42), repr("hi") # 'hi' with quotes -> "'hi'"
", ".join(["a","b","c"]) # 'a, b, c' — join, not +=
"".join(reversed(s))
"a-b-c".split("-") # ['a','b','c']
"a b c".split() # any whitespace, drops empties
"key=val=x".partition("=") # ('key','=','val=x')
"line1\nline2".splitlines()
"hello".encode("utf-8") # str -> bytes
b"hello".decode("utf-8") # bytes -> str
ord("A"), chr(65) # 65, 'A'| method | does |
|---|---|
| strip / lstrip / rstrip | trim chars (default whitespace) |
| removeprefix / removesuffix | drop exact prefix/suffix (3.9+) |
| upper lower title capitalize | case transforms |
| casefold | aggressive lower for comparison |
| startswith / endswith | accepts tuple of options |
| find / rfind | index or -1 |
| index / rindex | index or ValueError |
| replace(old,new,count) | substitution |
| count(sub) | non-overlapping occurrences |
| isdigit isalpha isalnum | content tests |
| isspace isidentifier | more tests |
| zfill(w) / ljust / rjust / center | padding |
| translate(str.maketrans(...)) | char mapping/removal |
"".join() for O(n).f-strings & Format Speccore
name, pi, n = "ada", 3.14159, 1234567
f"{name.upper()}" # expressions allowed
f"{pi:.2f}" # '3.14'
f"{n:,}" # '1,234,567'
f"{n:_}" # '1_234_567'
f"{0.257:.1%}" # '25.7%'
f"{n:>12}" # right-align width 12
f"{n:<12}" f"{n:^12}" # left / center
f"{n:012d}" # zero-pad
f"{255:#06x}" # '0x00ff'
f"{1e9:.2e}" # '1.00e+09'
f"{pi:{'.3f'}}" # nested format spec
f"{name=}" # "name='ada'" — debug (3.8+)
f"{obj!r}" # repr(); !s str(); !a ascii()
f"{{literal braces}}" # '{literal braces}'
f"{now:%Y-%m-%d}" # datetime passthrough| spec | [[fill]align][sign][#][0][width][,][.prec][type] |
|---|---|
| < > ^ = | align left/right/center/pad-after-sign |
| + - space | sign handling |
| b o x X | bin, octal, hex |
| d n | decimal, locale-aware |
| e f g % | sci, fixed, general, percent |
| *<10 | fill with '*', left, width 10 |
Legacy: "{} {}".format(a, b), "%s %d" % (s, n). Prefer f-strings.
Control Flow & matchcore
if x > 0: pass
elif x < 0: pass
else: pass
val = "yes" if cond else "no" # ternary
for i, item in enumerate(items, start=1): ...
for a, b in zip(xs, ys): ... # stops at shortest
for a, b in zip(xs, ys, strict=True): ... # raise on mismatch
for k, v in d.items(): ...
for x in reversed(seq): ...
for x in sorted(seq, key=len): ...
range(5), range(2, 10), range(10, 0, -2)
while n > 0:
n -= 1
if skip: continue
if done: break
else:
... # runs iff loop was NOT broken (for...else too)
# structural pattern matching (3.10+)
match command.split():
case ["go", direction]: print(direction)
case ["drop", *items]: print(items)
case ["quit" | "exit"]: raise SystemExit
case {"action": act, **rest}: ... # dict pattern
case Point(x=0, y=0): ... # class pattern
case [Point(x=0), Point() as p2]: ... # capture with as
case int() | float() as num if num > 0: ... # guard
case _: print("unknown")case names bind — a bare case x: matches anything and assigns. Use dotted names (case Color.RED:) to compare against constants.Listsmutable · ordered
xs = [1, 2, 3]
xs.append(4) # add one O(1)
xs.extend([5, 6]) # add many O(k)
xs.insert(0, 99) # at index O(n)
xs.pop() # remove+return last O(1)
xs.pop(0) # from front O(n) — use deque
xs.remove(99) # first matching value, ValueError if absent
xs.index(3), xs.count(2)
xs.reverse(); xs.sort() # in-place, return None!
ys = sorted(xs) # new list
ys = sorted(xs, key=len, reverse=True)
ys = sorted(people, key=lambda p: (p.age, p.name)) # multi-key
xs.clear()
# slicing: seq[start:stop:step] — stop exclusive
xs[2:5]; xs[:3]; xs[-3:]; xs[::2]; xs[::-1]
xs[1:3] = [9, 9, 9] # slice assignment resizes
del xs[::2] # delete by slice
copy = xs[:] # shallow copy (also list(xs), xs.copy())
import copy as _c; deep = _c.deepcopy(nested)
[0] * 5 # [0,0,0,0,0]
[[0]*3 for _ in range(2)] # 2-D — NOT [[0]*3]*2 (shared rows!)
max(xs, default=0); sum(xs); any(xs); all(xs)
list(filter(None, xs)) # drop falsyTuples & Unpackingimmutable
t = (1, "a", 3.0)
single = (42,) # ← comma makes the tuple, not parens
t = 1, 2, 3 # parens optional
x, y, z = t # unpack (exact count)
first, *mid, last = range(6) # mid = [1,2,3,4]
(a, b), c = (1, 2), 3 # nested
def stats(xs): return min(xs), max(xs) # "multi-return"
lo, hi = stats(data)
# tuples are hashable (if contents are) -> dict keys, set items
points = {(0, 0): "origin"}
from collections import namedtuple
Point = namedtuple("Point", "x y")
p = Point(3, 4); p.x; p._replace(x=9); p._asdict()
from typing import NamedTuple
class Point(NamedTuple): # typed version
x: int
y: int = 0Tuple vs list: heterogeneous fixed record vs homogeneous variable collection. Tuples are lighter and hashable.
Dictsinsertion-ordered
d = {"a": 1, "b": 2}
d = dict(a=1, b=2)
d = dict(zip(keys, vals))
d["c"] = 3
d["missing"] # KeyError
d.get("missing") # None
d.get("missing", 0) # default
d.setdefault("k", []).append(x) # init-if-absent idiom
d.pop("a") # remove+return (KeyError if absent)
d.pop("a", None) # safe
d.popitem() # remove last-inserted (k, v)
del d["b"]
"a" in d # key membership
d.keys(); d.values(); d.items() # live views
d.update(other) # merge in-place
merged = d1 | d2 # merged dict (3.9+), right wins
d1 |= d2 # in-place merge
{v: k for k, v in d.items()} # invert
dict.fromkeys(["a","b"], 0) # {'a':0,'b':0}
max(d, key=d.get) # key of max value
sorted(d.items(), key=lambda kv: kv[1], reverse=True)
{k: d[k] for k in sorted(d)} # sort by keylist(d) first.Setsunique · unordered
s = {1, 2, 3}
s = set() # {} is an empty DICT
s = set("hello") # {'h','e','l','o'}
s.add(4); s.discard(9) # discard: no error if absent
s.remove(9) # KeyError if absent
s.pop() # arbitrary element
x in s # O(1) membership — the killer feature
a | b a.union(b) # union
a & b a.intersection(b) # intersection
a - b a.difference(b) # difference
a ^ b a.symmetric_difference(b)
a <= b a.issubset(b); a < b # proper subset
a >= b a.issuperset(b)
a.isdisjoint(b)
a |= b; a &= b; a -= b # in-place variants
unique = list(set(xs)) # dedupe (order lost)
list(dict.fromkeys(xs)) # dedupe, order KEPT
frozenset({1, 2}) # immutable, hashable setSet elements must be hashable — use frozenset or tuples for nested sets.
Comprehensionsexpressive
[x**2 for x in range(10)] # list
[x for x in xs if x > 0] # filter
[x if x > 0 else 0 for x in xs] # map w/ ternary
{x % 3 for x in xs} # set
{w: len(w) for w in words} # dict
(x**2 for x in xs) # generator — lazy!
sum(x*x for x in xs) # genexp inline
# nested loops read left→right like for-statements
[(i, j) for i in range(3) for j in range(3) if i != j]
flat = [x for row in matrix for x in row] # flatten
transposed = [list(col) for col in zip(*matrix)]
# double comprehension = nested output
[[x*2 for x in row] for row in matrix]
# with walrus — compute once, use twice
[y for x in data if (y := f(x)) is not None]for/if clauses or side effects, write a real loop. Comprehensions are for building collections, not executing code.collectionsstdlib
from collections import Counter, defaultdict, deque, ChainMap
c = Counter("mississippi")
c.most_common(2) # [('i',4),('s',4)]
c["m"]; c["zz"] # missing -> 0, no KeyError
c.update(other); c.total()
Counter(a) + Counter(b); Counter(a) - Counter(b)
c1 & c2; c1 | c2 # min / max of counts
dd = defaultdict(list) # factory called on missing key
dd["k"].append(1) # no KeyError, auto []
graph = defaultdict(set) # adjacency lists
counts = defaultdict(int) # counting (or use Counter)
dq = deque([1,2,3], maxlen=5) # ring buffer w/ maxlen
dq.append(4); dq.appendleft(0) # O(1) both ends
dq.pop(); dq.popleft()
dq.rotate(1) # [3,0,1] style rotation
cm = ChainMap(cli_args, env, defaults) # layered lookup
cm["key"] # searches maps left→rightdeque for queues/BFS (list.pop(0) is O(n)); Counter for any tally; defaultdict for grouping.
# grouping idiom
by_len = defaultdict(list)
for w in words: by_len[len(w)].append(w)
import heapq # honorary mention: heaps
heapq.heapify(xs) # min-heap, in place
heapq.heappush(xs, v); heapq.heappop(xs)
heapq.nlargest(3, xs, key=score) # top-k
import bisect
bisect.bisect_left(sorted_xs, v) # binary search insert point
bisect.insort(sorted_xs, v)Functionscore
def f(a, b=10, *args, kw_only, opt=None, **kwargs):
"""Docstring: first line summary."""
return a + b
f(1, 2, 3, 4, kw_only=True, extra=9)
# args=(3,4) kwargs={'extra': 9}
def g(pos_only, /, either, *, kw_only): ...
# before / : positional-only; after * : keyword-only
f(*[1, 2], **{"kw_only": True}) # unpack into call
square = lambda x: x * x # single expression only
sorted(xs, key=lambda p: p[1])
def outer():
count = 0
def inner():
nonlocal count # write to enclosing scope
count += 1
return count
return inner # closure
counter = outer(); counter() # 1, 2, 3...
def h() -> int: ... # annotations (not enforced)
h.__doc__, h.__name__, h.__annotations__
# functions are objects: pass, store, return them
ops = {"add": lambda a,b: a+b, "mul": lambda a,b: a*b}
ops["add"](2, 3)def f(xs=[]) shares ONE list across all calls. Use xs=None then xs = xs if xs is not None else [].Scope rule LEGB: Local → Enclosing → Global → Builtins. global name / nonlocal name to rebind outward.
Decoratorsmeta
import functools, time
def timed(fn):
@functools.wraps(fn) # preserve __name__/__doc__
def wrapper(*args, **kwargs):
t0 = time.perf_counter()
try:
return fn(*args, **kwargs)
finally:
print(f"{fn.__name__}: {time.perf_counter()-t0:.4f}s")
return wrapper
@timed
def work(): ... # work = timed(work)
def retry(times=3, exc=Exception): # decorator WITH args
def deco(fn):
@functools.wraps(fn)
def wrapper(*a, **kw):
for i in range(times):
try: return fn(*a, **kw)
except exc:
if i == times - 1: raise
return wrapper
return deco
@retry(times=5, exc=ConnectionError)
def fetch(url): ...
@functools.lru_cache(maxsize=None) # or @functools.cache
def fib(n): return n if n < 2 else fib(n-1) + fib(n-2)
fib.cache_info(); fib.cache_clear()
# stacking: applied bottom-up
@timed
@retry()
def job(): ... # job = timed(retry()(job))Class-level: @staticmethod, @classmethod, @property. A decorator is just f = deco(f).
Classes & OOPobjects
class Animal:
kind = "generic" # CLASS attr (shared)
def __init__(self, name):
self.name = name # instance attr
def speak(self): # instance method
return f"{self.name} makes a sound"
@classmethod
def from_dict(cls, d): # alt constructor pattern
return cls(d["name"])
@staticmethod
def is_valid(name): # no self/cls
return bool(name)
@property
def label(self): # computed attr: a.label
return self.name.title()
@label.setter
def label(self, v): self.name = v.lower()
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # call parent init
self.breed = breed
def speak(self): # override
return super().speak() + " — woof"
d = Dog("rex", "gsd")
isinstance(d, Animal) # True
issubclass(Dog, Animal) # True
Dog.__mro__ # method resolution order (C3)
vars(d) # instance __dict__
getattr(d, "name", None); setattr(d, "x", 1); hasattr(d, "x")
class Point:
__slots__ = ("x", "y") # no __dict__: less RAM, no dynamic attrs
from abc import ABC, abstractmethod
class Repo(ABC):
@abstractmethod
def get(self, id): ... # subclasses MUST implement__attr becomes _Class__attr (avoid unless preventing subclass clashes). Convention: _attr = "internal, hands off".Dataclasses3.7+
from dataclasses import dataclass, field, asdict, replace
@dataclass
class User:
name: str
age: int = 0
tags: list[str] = field(default_factory=list) # mutable default
_cache: dict = field(default_factory=dict, repr=False,
compare=False)
def __post_init__(self): # validation/derivation
if self.age < 0: raise ValueError("age")
u = User("ada", 36)
u == User("ada", 36) # True — __eq__ generated
asdict(u) # recursive dict
replace(u, age=37) # copy with changes
@dataclass(frozen=True) # immutable + hashable
class Point:
x: float
y: float
@dataclass(order=True) # generates < <= > >=
class Task:
priority: int
name: str = field(compare=False)
@dataclass(slots=True) # 3.10+: __slots__ generated
class Fast: x: int; y: int
@dataclass(kw_only=True) # 3.10+: all fields keyword-only
class Config: host: str; port: int = 8080Generated for free: __init__, __repr__, __eq__ (+ __hash__ if frozen, ordering if order=True). Heavier alternative with validation: pydantic.
Dunder Methodsprotocols
| method | triggered by |
|---|---|
| __init__ / __new__ | construction (init self / create instance) |
| __repr__ | repr(x), debugger — unambiguous |
| __str__ | str(x), print — readable (falls back to repr) |
| __eq__ __hash__ | ==, dict/set keys (eq without hash → unhashable) |
| __lt__ __le__ __gt__ __ge__ | ordering, sorted() |
| __len__ __bool__ | len(x), truthiness (len→bool fallback) |
| __getitem__ __setitem__ __delitem__ | x[k] access |
| __contains__ | v in x |
| __iter__ __next__ | for loops, iter()/next() |
| __call__ | x() — callable instances |
| __enter__ __exit__ | with blocks (context manager) |
| __add__ __radd__ __iadd__ | x+y, y+x fallback, x+=y (same for sub/mul/truediv/…) |
| __neg__ __abs__ __round__ | -x, abs(x), round(x) |
| __getattr__ | attr lookup MISS only |
| __getattribute__ | every attr lookup (dangerous) |
| __slots__ | declared attrs, no __dict__ |
| __init_subclass__ | subclass creation hook |
| __class_getitem__ | Class[param] generics |
class Vector:
def __init__(self, *xs): self.xs = tuple(xs)
def __repr__(self): return f"Vector{self.xs}"
def __len__(self): return len(self.xs)
def __getitem__(self, i): return self.xs[i] # → iterable, sliceable
def __add__(self, o):
if not isinstance(o, Vector): return NotImplemented
return Vector(*(a+b for a, b in zip(self.xs, o.xs)))
def __eq__(self, o): return isinstance(o, Vector) and self.xs == o.xs
def __hash__(self): return hash(self.xs)Return NotImplemented (not raise) from binary ops on type mismatch — lets Python try the reflected method.
Iterators & Generatorslazy
it = iter([1, 2, 3])
next(it) # 1
next(it, None) # default instead of StopIteration
def countdown(n): # generator function — lazy, resumable
while n > 0:
yield n # pauses here, resumes on next()
n -= 1
for x in countdown(3): print(x)
list(countdown(3)) # [3,2,1]
def chained(*iters):
for it in iters:
yield from it # delegate to sub-iterator
gen = (x*x for x in range(10**9)) # generator expression: O(1) mem
sum(gen) # consumes it — one pass only!
# generators as pipelines
lines = (l.strip() for l in open("log.txt"))
errors = (l for l in lines if "ERROR" in l)
first5 = [next(errors) for _ in range(5)]
# infinite generator
def ids():
n = 0
while True:
yield n; n += 1
# coroutine-ish: send values in
def accumulator():
total = 0
while True:
total += yield total
class Countdown: # iterator protocol by hand
def __init__(self, n): self.n = n
def __iter__(self): return self
def __next__(self):
if self.n <= 0: raise StopIteration
self.n -= 1; return self.n + 1list(). Check exhaustion bugs when a "sequence" is silently empty the second time.itertoolsstdlib
from itertools import *| fn | yields |
|---|---|
| count(10, 2) | 10, 12, 14, … infinite |
| cycle("AB") | A B A B … infinite |
| repeat(x, 3) | x, x, x |
| chain(a, b) | all of a, then b |
| chain.from_iterable(m) | flatten one level |
| islice(it, 5) / (it, 2, 8) | lazy slicing of any iterator |
| takewhile(pred, it) | prefix while true |
| dropwhile(pred, it) | skip prefix, rest |
| filterfalse(pred, it) | where pred is False |
| accumulate(xs) / (xs, mul) | running totals / fold prefixes |
| pairwise(xs) | (a,b),(b,c),(c,d) — 3.10+ |
| batched(xs, 3) | chunks of 3 — 3.12+ |
| zip_longest(a, b, fillvalue=0) | zip to longest |
| product("AB", repeat=2) | AA AB BA BB (cartesian) |
| permutations("ABC", 2) | ordered picks, no repeat |
| combinations("ABC", 2) | AB AC BC |
| combinations_with_replacement | AA AB AC BB BC CC |
| groupby(xs, key) | (key, group-iter) — SORT FIRST |
| starmap(f, pairs) | f(*pair) for each |
| tee(it, 2) | n independent copies |
# groupby only groups CONSECUTIVE equal keys:
data.sort(key=keyfn)
for k, grp in groupby(data, key=keyfn):
print(k, list(grp))functoolsstdlib
from functools import (reduce, partial, cache, lru_cache,
cached_property, wraps, total_ordering, singledispatch)
reduce(lambda acc, x: acc * x, [1,2,3,4], 1) # 24
pow2 = partial(pow, 2) # freeze leading args
pow2(10) # 1024
log_err = partial(log, level="ERROR")
@cache # unbounded memoize (3.9+)
def parse(s): ...
@lru_cache(maxsize=256) # bounded LRU
def lookup(key): ... # args must be hashable
class Report:
@cached_property # computed once, then stored
def stats(self): return expensive(self.data)
@total_ordering # define __eq__ + one of lt/le/gt/ge
class Version: # → get all comparisons
def __eq__(self, o): ...
def __lt__(self, o): ...
@singledispatch # function overloading by arg type
def render(x): return str(x)
@render.register
def _(x: list): return ", ".join(map(render, x))
@render.register
def _(x: dict): return "; ".join(f"{k}={v}" for k,v in x.items())
import operator as op # frequent partner
sorted(xs, key=op.itemgetter(1))
sorted(objs, key=op.attrgetter("age", "name"))
reduce(op.add, xs)Files & I/Oio
with open("data.txt", encoding="utf-8") as f: # auto-close
text = f.read() # whole file
# f.readline() # one line
# f.readlines() # list of lines
with open("data.txt") as f:
for line in f: # memory-efficient iteration
process(line.rstrip("\n"))
with open("out.txt", "w", encoding="utf-8") as f:
f.write("one\n")
f.writelines(f"{x}\n" for x in xs)
print("also works", file=f)
with open("img.png", "rb") as f: # binary
blob = f.read()
with open("a") as fa, open("b") as fb: # multiple
...
f.seek(0); f.tell() # random access
import io
buf = io.StringIO("in-memory text file")
raw = io.BytesIO(b"bytes buffer")| mode | meaning |
|---|---|
| r | read (default), must exist |
| w | write, TRUNCATES / creates |
| a | append, creates |
| x | create, fail if exists |
| r+ / w+ | read+write (w+ truncates) |
| rb wb ab | binary variants (no encoding) |
encoding="utf-8" for text — the default is platform-dependent.pathlibpaths
from pathlib import Path
p = Path("data") / "raw" / "file.tar.gz" # / joins!
p.name # 'file.tar.gz'
p.stem # 'file.tar'
p.suffix # '.gz'
p.suffixes # ['.tar', '.gz']
p.parent # Path('data/raw')
p.parts # ('data', 'raw', 'file.tar.gz')
p.with_suffix(".zip"); p.with_name("other.txt")
Path.cwd(); Path.home(); p.resolve(); p.absolute()
p.exists(); p.is_file(); p.is_dir(); p.is_symlink()
p.stat().st_size; p.stat().st_mtime
p.read_text(encoding="utf-8") # slurp
p.write_text("content", encoding="utf-8")
p.read_bytes(); p.write_bytes(b"...")
d = Path("out"); d.mkdir(parents=True, exist_ok=True)
p.unlink(missing_ok=True) # delete file
d.rmdir() # empty dir only
p.rename("new"); p.replace("new") # replace overwrites
import shutil
shutil.rmtree(d); shutil.copy2(src, dst); shutil.move(src, dst)
for f in d.iterdir(): ... # children
for f in d.glob("*.py"): ... # pattern
for f in d.rglob("**/*.json"): ... # recursive
[f for f in d.rglob("*") if f.is_file()]JSON · CSV · Pickledata
import json
s = json.dumps(obj, indent=2, sort_keys=True)
s = json.dumps(obj, default=str) # fallback for datetime etc.
obj = json.loads(s)
with open("f.json", "w") as f: json.dump(obj, f, indent=2)
with open("f.json") as f: obj = json.load(f)
# str↔str; dict keys become strings; tuples become lists
import csv
with open("f.csv", newline="") as f: # newline="" matters
for row in csv.reader(f): ... # row = list[str]
# or:
for row in csv.DictReader(f): row["col_name"]
with open("out.csv", "w", newline="") as f:
w = csv.writer(f); w.writerow(["a", "b"]); w.writerows(rows)
dw = csv.DictWriter(f, fieldnames=["a","b"])
dw.writeheader(); dw.writerow({"a":1,"b":2})
import pickle # arbitrary Python objects
blob = pickle.dumps(obj)
obj = pickle.loads(blob) # NEVER on untrusted datapickle.loads executes arbitrary code — only unpickle data you produced. For config prefer JSON/TOML (tomllib.load, 3.11+).Regexre
import re
pat = re.compile(r"(\w+)@(\w+)\.com") # compile if reused
m = pat.search(text) # first match anywhere, or None
if m:
m.group(0) # whole match
m.group(1), m.group(2) # captures
m.start(), m.end(), m.span()
pat.match(text) # only at START
pat.fullmatch(text) # entire string
pat.findall(text) # list of captures/strings
pat.finditer(text) # iterator of match objects
pat.split("a1b22c") # split by pattern
pat.sub(r"\2/\1", text) # replace w/ backrefs
pat.sub(lambda m: m.group(1).upper(), text) # fn replacement
m = re.search(r"(?P\w+)@(?P\w+)", s)
m["user"] # named groups
re.IGNORECASE | re.MULTILINE # flags: re.I, re.M (^$ per line),
# re.S (dot matches \n), re.X (verbose) | pattern | matches |
|---|---|
| . ^ $ | any char (not \n), start, end |
| * + ? {2,5} | 0+, 1+, 0/1, range — all greedy |
| *? +? ?? | lazy variants |
| \d \w \s | digit, word char, whitespace (caps = negated) |
| [abc] [^abc] [a-z] | char class, negated, range |
| \b | word boundary |
| (x) (?:x) | capture / non-capture group |
| a|b | alternation |
| (?=x) (?!x) | lookahead pos/neg |
| (?<=x) (?<!x) | lookbehind pos/neg |
| \1 | backreference to group 1 |
datetimetime
from datetime import datetime, date, timedelta, timezone
now = datetime.now() # naive local
utc = datetime.now(timezone.utc) # aware — prefer!
today = date.today()
dt = datetime(2026, 7, 13, 14, 30)
dt.year, dt.month, dt.day, dt.hour, dt.weekday() # Mon=0
dt.isoformat() # '2026-07-13T14:30:00'
datetime.fromisoformat("2026-07-13T14:30:00+01:00")
dt.strftime("%Y-%m-%d %H:%M") # format → str
datetime.strptime("13/07/26", "%d/%m/%y") # parse ← str
delta = timedelta(days=7, hours=3)
tomorrow = now + timedelta(days=1)
(d2 - d1).days; delta.total_seconds()
from zoneinfo import ZoneInfo # 3.9+
lagos = datetime.now(ZoneInfo("Africa/Lagos"))
lagos.astimezone(ZoneInfo("America/Los_Angeles"))
import time
time.time() # unix epoch float
time.perf_counter() # monotonic — use for timing
time.sleep(0.5)
datetime.fromtimestamp(1752400000, tz=timezone.utc)
dt.timestamp()| code | meaning |
|---|---|
| %Y %m %d | 2026, 07, 13 |
| %H %M %S | hour24 min sec |
| %I %p | hour12, AM/PM |
| %a %A | Mon / Monday |
| %b %B | Jul / July |
| %j %U %W | day-of-year, week# |
| %z %Z | +0100, TZ name |
| %% | literal % |
Type Hintstyping
def scale(v: list[float], k: float = 1.0) -> list[float]: ...
x: int | None = None # Optional (3.10+ union syntax)
pairs: dict[str, list[int]] = {}
point: tuple[int, int]; row: tuple[str, ...] # variadic
from typing import (Any, Callable, Literal, Final, TypeVar,
Protocol, TypedDict, TypeAlias, cast, overload, Iterator)
from collections.abc import Iterable, Sequence, Mapping
f: Callable[[int, str], bool]
mode: Literal["r", "w", "a"]
MAX: Final = 100 # constant — no reassignment
T = TypeVar("T")
def first(xs: Sequence[T]) -> T: return xs[0]
def first[T](xs: Sequence[T]) -> T: ... # 3.12 syntax
class Comparable(Protocol): # structural typing (duck)
def __lt__(self, other) -> bool: ...
def smallest(xs: Iterable[Comparable]): ...
class Movie(TypedDict): # typed dict shapes
title: str
year: int
rating: NotRequired[float] # 3.11+
Vector: TypeAlias = list[float]
type Vector = list[float] # 3.12 syntax
def gen() -> Iterator[int]: yield 1
val = cast(list[int], opaque) # assert to checker, no runtime op
@overload
def get(i: int) -> str: ...
@overload
def get(i: slice) -> list[str]: ...
def get(i): ... # real impl lastHints are ignored at runtime — enforce with mypy / pyright. Accept broad (Iterable, Mapping), return specific (list, dict).
Exceptionserrors
try:
risky()
except (ValueError, KeyError) as e: # tuple of types
logger.warning("bad input: %s", e)
except OSError as e:
if e.errno != errno.ENOENT: raise # re-raise same exc
except Exception:
logger.exception("unexpected") # logs traceback
raise # never swallow silently
else:
... # runs only if NO exception
finally:
... # always runs (cleanup)
raise ValueError(f"bad port: {port!r}")
raise TimeoutError("db") from original_exc # chain: __cause__
raise RuntimeError from None # suppress context
class AppError(Exception): ... # custom hierarchy
class ConfigError(AppError):
def __init__(self, key, msg="missing"):
self.key = key
super().__init__(f"{key}: {msg}")
assert cond, "message" # debug-time; stripped by python -O
from contextlib import suppress
with suppress(FileNotFoundError): # explicit "ignore"
os.remove(tmp)
try: # 3.11+: exception groups
parallel_work()
except* ValueError as eg: # handles matching subgroup
...| common | raised when |
|---|---|
| ValueError | right type, bad value |
| TypeError | wrong type |
| KeyError / IndexError | missing dict key / seq index (both ⊂ LookupError) |
| AttributeError | missing attribute |
| FileNotFoundError / PermissionError | ⊂ OSError |
| StopIteration | iterator exhausted |
| KeyboardInterrupt / SystemExit | ⊂ BaseException, NOT Exception |
Modules & Importsstructure
import math
import numpy as np
from pathlib import Path
from collections import Counter, deque
from .sibling import helper # relative (inside package)
from ..parent import thing
if __name__ == "__main__": # run-as-script guard
main()
# package layout
# mypkg/
# __init__.py # runs on import; controls `from mypkg import *`
# core.py
# utils/
# __init__.py
# text.py # mypkg.utils.text
__all__ = ["public_fn"] # star-import surface
import importlib
importlib.reload(module) # re-exec (REPL/dev)
mod = importlib.import_module("pkg.name") # dynamic
import sys
sys.path # module search paths
sys.modules # import cache
# lazy import for heavy deps
def plot(data):
import matplotlib.pyplot as plt # only paid when called
...from __future__ import annotations for type-only cycles.venv · pip · Toolingenv
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
deactivate
pip install requests "django>=4.2,<5" pkg==1.2.3
pip install -e . # editable local install
pip install -r requirements.txt
pip freeze > requirements.txt
pip list --outdated; pip show requests; pip uninstall pkg
# uv — fast modern manager (recommended)
uv venv && uv pip install requests
uv add requests && uv run script.py
uv sync # from pyproject/lockfile
python -m module # run module as script
python -c "import sys; print(sys.version)"
python -i script.py # drop into REPL after
python -X dev script.py # dev mode: extra checks# pyproject.toml (modern standard)
[project]
name = "mypkg"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["httpx", "pydantic>=2"]
[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]Quality stack: ruff (lint+format, replaces flake8/black/isort), mypy/pyright (types), pytest (tests), pre-commit (hooks).
asyncioasync/await
import asyncio
async def fetch(url): # coroutine function
await asyncio.sleep(1) # yield control — never time.sleep!
return url
async def main():
r = await fetch("a") # sequential
a, b = await asyncio.gather( # concurrent
fetch("a"), fetch("b"))
rs = await asyncio.gather(*tasks, return_exceptions=True)
t = asyncio.create_task(fetch("bg")) # start now
...
result = await t
async with asyncio.timeout(5): # 3.11+
await slow()
r = await asyncio.wait_for(slow(), timeout=5) # older
async with asyncio.TaskGroup() as tg: # 3.11+ structured
t1 = tg.create_task(fetch("a")) # auto-await + cancel
t2 = tg.create_task(fetch("b")) # errors → ExceptionGroup
asyncio.run(main()) # entry point — once
sem = asyncio.Semaphore(10) # limit concurrency
async def bounded(url):
async with sem:
return await fetch(url)
async for item in aiter: ... # async iteration
async with session.get(url) as r: # async context manager
q = asyncio.Queue()
await q.put(x); x = await q.get(); q.task_done()
# CPU-bound / blocking calls: don't block the loop
await asyncio.to_thread(blocking_io, arg)
loop = asyncio.get_running_loop()
await loop.run_in_executor(None, cpu_task)await points only. A forgotten await gives you a coroutine object, not a result.Threads & Processesparallel
from concurrent.futures import (ThreadPoolExecutor,
ProcessPoolExecutor, as_completed)
# I/O-bound → threads (GIL released during I/O)
with ThreadPoolExecutor(max_workers=8) as ex:
results = list(ex.map(fetch, urls)) # ordered
futs = {ex.submit(fetch, u): u for u in urls}
for fut in as_completed(futs): # arrival order
try: print(futs[fut], fut.result())
except Exception as e: print("failed:", e)
# CPU-bound → processes (sidesteps the GIL)
with ProcessPoolExecutor() as ex: # picklable args only
out = list(ex.map(crunch, chunks, chunksize=100))
import threading
lock = threading.Lock()
with lock: # guard shared mutable state
counter += 1
threading.RLock(); threading.Event(); threading.Semaphore(5)
t = threading.Thread(target=work, args=(q,), daemon=True)
t.start(); t.join()
from queue import Queue # thread-safe channel
q = Queue()
q.put(item); item = q.get(); q.task_done(); q.join()
import multiprocessing as mp
if __name__ == "__main__": # REQUIRED on spawn platforms
with mp.Pool() as pool:
pool.map(f, data)subprocess · os · syssystem
import subprocess as sp
r = sp.run(["git", "status"], capture_output=True,
text=True, check=True, timeout=30)
r.stdout, r.stderr, r.returncode
# check=True → raises CalledProcessError on non-zero
out = sp.check_output(["ls", "-la"], text=True)
p = sp.Popen(cmd, stdout=sp.PIPE) # streaming/async control
for line in p.stdout: ...
# avoid shell=True with untrusted input (injection)
import sys
sys.argv # [script, arg1, ...]
sys.exit(1) # exit code
sys.stdin.read(); print("err", file=sys.stderr)
sys.version_info >= (3, 11)
sys.getsizeof(obj) # shallow size in bytes
import os
os.environ.get("API_KEY", "")
os.environ["MODE"] = "prod"
os.getpid(); os.cpu_count(); os.getcwd(); os.chdir(p)
os.urandom(16) # crypto-grade bytes
import argparse # CLI args
ap = argparse.ArgumentParser(description="tool")
ap.add_argument("path")
ap.add_argument("-n", "--count", type=int, default=1)
ap.add_argument("-v", action="store_true")
args = ap.parse_args(); args.countTesting (pytest)quality
# test_calc.py — files/functions must start with test_
def test_add():
assert add(2, 3) == 5 # plain assert, rich diffs
def test_raises():
import pytest
with pytest.raises(ValueError, match="negative"):
sqrt(-1)
@pytest.mark.parametrize("a,b,want", [
(1, 2, 3), (0, 0, 0), (-1, 1, 0),
])
def test_add_many(a, b, want):
assert add(a, b) == want
@pytest.fixture
def db(): # setup/teardown
conn = connect(":memory:")
yield conn # ← test runs here
conn.close()
def test_query(db): # injected by name
assert db.query("...") == []
def test_float():
assert 0.1 + 0.2 == pytest.approx(0.3)
@pytest.fixture(scope="module") # share across a module
def server(): ...
@pytest.mark.skip(reason="wip")
@pytest.mark.xfail # expected failure
# built-in fixtures: tmp_path, monkeypatch, capsys
def test_env(monkeypatch, tmp_path, capsys):
monkeypatch.setenv("MODE", "test")
monkeypatch.setattr(mod, "fetch", lambda u: "stub")
(tmp_path / "f.txt").write_text("x")
print("hi"); assert capsys.readouterr().out == "hi\n"
from unittest.mock import Mock, patch
m = Mock(return_value=42); m(1); m.assert_called_once_with(1)
with patch("mymod.requests.get") as g:
g.return_value.json.return_value = {"ok": True}pytest -x -q # stop at first failure, quiet
pytest -k "add and not slow" # filter by name
pytest --lf # rerun last failures
pytest -s # show prints
pytest --cov=mypkg # coverage (pytest-cov)Debug & Profileperf
breakpoint() # drop into pdb here (3.7+)
# pdb: n(ext) s(tep) c(ontinue) l(ist) p expr w(here) q(uit)
# up/down = walk stack; b file:42 = breakpoint
python -m pdb script.py # post-mortem from start
import pdb; pdb.pm() # inspect last traceback
import timeit
timeit.timeit("x in s", setup="s=set(range(1000)); x=999",
number=100_000)
# shell: python -m timeit "'-'.join(map(str, range(100)))"
import cProfile, pstats
cProfile.run("main()", "out.prof")
pstats.Stats("out.prof").sort_stats("cumulative").print_stats(15)
# shell: python -m cProfile -s tottime script.py
import tracemalloc # memory
tracemalloc.start()
...
for s in tracemalloc.take_snapshot().statistics("lineno")[:10]:
print(s)
import logging
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s")
log = logging.getLogger(__name__)
log.debug("x=%s", x); log.info(...); log.warning(...)
log.exception("boom") # inside except: adds traceback
import dis
dis.dis(fn) # bytecode — settle perf debates
from pprint import pprint; pprint(nested, width=100)
import reprlib; reprlib.repr(huge) # truncated reprOrder of operations: measure (profile) → find hot spot → fix algorithm/data structure → only then micro-optimize.
Gotchasread this
# 1. mutable default — shared across calls
def bad(x, acc=[]): acc.append(x); return acc
bad(1); bad(2) # [1, 2] !!
# 2. late-binding closures — all lambdas see final i
fns = [lambda: i for i in range(3)]
[f() for f in fns] # [2, 2, 2]
fns = [lambda i=i: i for i in range(3)] # fix: default-capture
# 3. is vs == ; small-int caching makes `is` "work" sometimes
a = 256; b = 256; a is b # True (cached -5..256)
a = 257; b = 257; a is b # False (usually) — use ==
# 4. shallow copy shares nested objects
row = [[0]*3]*3; row[0][0] = 9 # ALL rows change
import copy; safe = copy.deepcopy(nested)
# 5. mutating a list while iterating skips items
for x in xs: # BAD if removing
if bad(x): xs.remove(x)
xs[:] = [x for x in xs if not bad(x)] # good
# 6. tuple needs the comma
t = (1) # int 1!
t = (1,) # tuple
# 7. sort()/reverse()/append() return None
xs = xs.sort() # xs is now None !!
# 8. chained "equality" surprise
x == y == z # means x==y and y==z (usually wanted)
0.1 + 0.2 == 0.3 # False — math.isclose()
# 9. bool is an int subclass
True + True # 2 ; isinstance(True, int) → True
# 10. except swallowing everything
try: ...
except: pass # hides even KeyboardInterrupt — never
# 11. default args evaluated ONCE at def-time
def f(when=datetime.now()): ... # frozen timestamp!
# 12. string interning / identity
"a" * 1000 is "a" * 1000 # implementation detail — never relyIdioms & One-linerspythonic
a, b = b, a # swap
xs[::-1] # reverse copy
list(dict.fromkeys(xs)) # dedupe, keep order
[x for row in m for x in row] # flatten
list(zip(*m)) # transpose
dict(zip(keys, vals)) # pair-up
{**d1, **d2} or d1 | d2 # merge dicts
max(set(xs), key=xs.count) # mode (small lists)
Counter(xs).most_common(1)[0][0] # mode (proper)
sum(1 for x in xs if pred(x)) # count matching
next((x for x in xs if pred(x)), None) # first match or None
all(x > 0 for x in xs); any(...)
sorted(xs, key=str.lower) # case-insensitive
min(points, key=lambda p: p[0]**2+p[1]**2)
[xs[i:i+n] for i in range(0, len(xs), n)] # chunk
s == s[::-1] # palindrome
" ".join(w.capitalize() for w in s.split())
print(*xs, sep=", ") # no join needed
x = val if cond else other
value = d.get(key) or default # careful: falsy values!
value = d.get(key, default) # usually what you want
globals().get("DEBUG", False)
isinstance(x, (list, tuple))
path.read_text().splitlines() # file → lines
try: return cache[k]
except KeyError: ... # EAFP over LBYL
# unpack in loops
for (a, b), c in zip(pairs, cs): ...
# conditional expression in f-string
f"{'yes' if ok else 'no'}"
# ternary chain? no — use a dict dispatch
handler = {"add": do_add, "del": do_del}[cmd]