Summary
Functions are the unit of reuse, and the three properties that make them reusable are one job, an honest name, and a predictable return. Most working functions share the same internal shape: take input, validate it and reject early, transform, construct one result and return it. Validation at the top means the transform section carries no defensive branches.
Keyword-only arguments prevent the swapped-argument bug and let you change a signature later without breaking callers. *args and **kwargs exist for genuine pass-through, not for avoiding a decision about what a function accepts.
Pure functions return the same output for the same input and change nothing outside themselves, which makes them testable with a call and an assertion. Side effects cannot be removed, but they can be pushed to a thin impure shell around a pure core that holds all the decisions. That single structural idea determines how testable a codebase is.
Functions are values, so they can be stored, passed, and returned. sorted(key=...) is the most common place you will use that. Lambdas belong inline and nowhere else. Closures capture values from their creation scope, and factory functions use that to build configured functions, which is how a pipeline becomes configurable without any transform changing.
A decorator is a function that takes a function and returns a replacement, built from closures and argument forwarding. Always apply functools.wraps, or the wrapped function loses its name, docstring, and signature. lru_cache is a real win for pure, expensive, repeatedly called functions and a silent hazard for anything impure or time sensitive.
Prompts are code. Put them in a module as named template functions, version them when behaviour changes, record the version alongside the output, delimit untrusted content explicitly, and review prompt changes with evidence attached.
Key takeaways
- If you cannot describe a function without saying "and", it is doing more than one job
- A name that promises a read must not perform a write
- Make data positional and options keyword-only
- Pure functions are the testable ones, so keep the decisions pure and the side effects at the edges
- Composition works because types line up, which is what typed records bought you
- A lambda that deserves a name deserves to be a function
- A closure captures the variable, not its value, which is why factories beat lambdas in loops
- A decorator is just a function returning a replacement function, and
functools.wrapsis not optional - Caching an impure function makes its side effect happen exactly once
- A prompt buried in an f-string cannot be found, reviewed, tested, or reverted
- Instruction and untrusted content must be separated structurally, not by good intentions
Common mistakes to remember
- Writing a function that reads, transforms, saves, and reports
- Returning three different types from one function
- Using positional arguments for numbers and booleans
- Mutable default arguments
- Declaring
**kwargsinstead of deciding on a signature - Mixing decisions into the same function that does the input and output
- Assigning a lambda to a name
- Creating closures in a loop without capturing the loop variable
- Forgetting
functools.wrapsand losing every function name in your logs - Applying
lru_cacheto a function that writes something or reads changing data - Leaving
maxsize=Noneon a long-running process - Logging argument values in a decorator that will eventually wrap something holding a secret
- Interpolating user content directly into an instruction string
- Editing a prompt in place when stored outputs depend on the old wording