16 · Functions: define, call, parameters, return
Write reusable, testable building blocks — parameters, defaults, return values, scope, and the mutable-default trap that every Python developer hits once.
A function is a named, reusable block of code — `def name(params): ...` — that accepts inputs, performs work, and optionally returns a value. Functions are the unit of abstraction in Python.
Without this:
Without functions, every repeated computation must be copy-pasted — changing the formula in one place doesn't fix the copies, bugs multiply, and experiments become unreproducible because the same logic runs slightly differently in two spots.
Define a function with def name(parameters): followed by an indented body. Call it by writing name(arguments). Without a return statement (or return alone) the function returns None. Parameters are positional by default; you can also pass them by name as keyword arguments: fn(x=1, y=2). Default values let you make parameters optional — but never use a mutable object (list, dict, set) as a default, because Python creates it once at function definition time and reuses the same object on every call.
Python (in browser)
A function with a docstring and a clear return value is immediately testable — you can call `mse([1,2],[1,2])` and verify it returns `0.0` without running a whole pipeline.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
All three calls share the same default list. This is Python's most famous footgun — `history=[]` is evaluated once when the `def` statement runs, not on each call.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
The canonical fix: use `None` as the default and create the mutable object inside the function body. Each call gets its own fresh list.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
LEGB is the order Python searches for a name: Local → Enclosing → Global → Built-in. `global` and `nonlocal` let you step outside the local scope to mutate names further up the chain.
What does an empty `def f(): pass` return when called as `f()`?
Implement metric functions, fix mutable-default bugs, and trace scope in the Arena with server-side tests.
- `def name(params): ... return value` — the full anatomy. Without `return`, the function returns `None`.
- Never use a mutable object (`[]`, `{}`, `set()`) as a default parameter. Use `None` and create the object inside the body.
- Python resolves names in LEGB order. Use `global` or `nonlocal` only when you need to mutate an outer-scope name — prefer returning values instead.
Every loss function, metric, and data transform in an ML codebase is a Python function: `mse(y_true, y_pred)`, `accuracy(y_true, y_pred)`, `normalize(x, mean, std)`. Reproducible experiments depend on pure functions — same inputs always produce the same outputs, with no hidden state mutated by mutable defaults.
If you remove it: Without functions, loss computations are inline expressions repeated across training, validation, and test loops — changing the formula requires finding and updating every copy, and a missed update silently evaluates a different metric in each split.