Python from First PrinciplesPost 4 of 20 · 20 of 20 written
- How to use this guide
- 01What Python actually is
- 02Names, objects and references
- 03Built-in types and what they cost
- 04Control flow and expressions
- 05Functions in depth
- 06Iteration and generators
- 07Classes and the data model
- 08Context managers and exceptions
- 09Modules, packages, imports and environments
- 10Type hints and Pydantic
- 11The performance model
- 12Concurrency and parallelism
- 13NumPy: the array model everything else copies
- 14Data and serialization
- 15PyTorch mental model
- 16AI systems engineering in Python
- 17Tooling, debugging and testing
- 18Asking the right questions
- 19Roadmap and build-to-learn projects
- 20Glossary and resources
4. Control flow and expressions
Python’s control flow is small; the leverage is in its idioms. Truthiness, comprehensions, unpacking and “ask forgiveness” error handling make up most idiomatic code.
Truthiness
if x: calls bool(x), which uses the object’s __bool__ method, or __len__ if there is none. The falsy values are None, False, 0, 0.0, "", [], {}, set() and empty ranges; everything else is truthy.
The trap: if not value: treats a valid 0 or "" as missing. A real bug: temperature = temperature or 0.7 silently turns a deliberate temperature of 0 into 0.7. When zero is a legitimate value, write if value is None:.
Boolean operators return operands
a or b returns a if it is truthy, otherwise b. a and b returns a if it is falsy, otherwise b. Both short-circuit: b is never evaluated if the answer is already known. Comparisons chain: 0 < lr < 1.
Loops
foriterates over any iterable (Part 6). There is no C-style index loop, and you rarely need one.enumerate(xs)gives index and value;zip(a, b, strict=True)walks sequences in parallel and raises if their lengths differ. Usestrict=Truefor predictions and labels, because a plainzipsilently stops at the shorter one.range(n)is lazy and uses constant memory.for ... else: theelseruns only if the loop finished withoutbreak. It is useful in search loops.whilesuits conditions, polling and retry loops.
Comprehensions
squares = [x * x for x in range(10) if x % 2 == 0]
vocab = {tok: i for i, tok in enumerate(tokens)}
unique = {w.lower() for w in words}
total = sum(len(doc) for doc in docs) # generator expression: no list is builtComprehensions are faster than the equivalent append loop and read as one idea. Keep them to one or two clauses; beyond that, write a normal loop.
Unpacking
x, y = point
first, *rest = tokens
a, b = b, a # swap without a temp variable
for i, (name, score) in enumerate(results.items()): ...
batch_size, seq_len, d_model = x.shape # everywhere in ML code
merged = {**defaults, **overrides} # later keys winConditional expressions and the walrus operator
label = "pos" if score > 0 else "neg"is an expression, so it fits inside other expressions.:=assigns inside an expression:while (chunk := f.read(8192)):reads until the file is empty.
Structural pattern matching (3.10+)
match destructures data by shape. It is handy for routing streamed LLM events and tool calls:
match event:
case {"type": "text", "text": str(t)}:
print(t, end="")
case {"type": "tool_use", "name": name, "input": args}:
run_tool(name, args)
case _:
passIt is not a C switch. Each pattern checks structure and binds names when it matches.
EAFP versus LBYL
- LBYL, “look before you leap”:
if key in d: v = d[key]. - EAFP, “easier to ask forgiveness than permission”:
try: v = d[key]followed byexcept KeyError:.
Python culture favors EAFP. It avoids race conditions (a file can vanish between the check and the open), and it is fast when failures are rare. Use LBYL when failure is common or the check is cheap and clearer.
Questions to ask:
- Can a valid value be falsy here?
- Should these sequences have equal length, and is
zipsilently truncating?