Read where the multiplication actually happens — it is not on the line that looks like it.
def multiplier(factor):
def multiply(value):
return value * factor # factor is remembered from outside
return multiply # the function itself, uncalled
triple = multiplier(3)
print(triple) # -> <function multiplier.<locals>.multiply ...>
print(triple(5)) # -> 15 the multiplication happens here
- The inner function is built, not run
multiplier(3) defines multiply and returns it. Nothing has been multiplied yet — that only happens when the returned function is called. Separating 'set this up' from 'do the work' is what makes the pattern useful.
- It remembers the values around it
multiply still has access to factor after multiplier has returned. That captured environment is a closure, and it is why the returned function needs no arguments for the things it was configured with.
- return multiply, never return multiply()
The brackets would call it immediately — with no argument — and either raise TypeError or hand back a value where a function was expected. It is the single most common typo in this whole pattern.
You are not computing anything. You are building something that will compute later — so the body of compose defines a function and hands it back.
Define an inner function taking one argument. Inside it, call inner first and pass the result to outer. Then return that function — without brackets.
def composed(value): return outer(inner(value)); then return composed. Both outer and inner are still visible inside composed because of the closure, so nothing needs to be stored or passed along.
def compose(outer, inner): def composed(value): return ____(____(value)) return ____
def compose(outer, inner):
def composed(value):
return outer(inner(value))
return composed
outer(inner(value)) runs inner first and outer last, because the innermost call has to produce something before the outer one can use it. That is why compose(f, g) means 'f after g' — it matches how the expression reads, not the order things happen. Naming the parameters outer and inner rather than f and g is what keeps that straight.
One of the cases composes two functions that record every call and then checks nothing was recorded. Getting this wrong — calling inner while building — is invisible for pure functions and disastrous for ones with side effects, which is exactly the class of bug that only appears in production.
A general version would take *args and **kwargs and pass them through to inner. That is more useful and less obvious, and the single-argument form is what makes the pattern legible while you are learning it. Widening it is the first extension for a reason.
Do not compose when a plain call would read better — outer(inner(x)) written once is clearer than building a function to use once. Composition pays when the pipeline is named and reused, or when something else needs to accept a single callable. Note also that the composed function loses its name and docstring in tracebacks; for anything that will be debugged, functools.wraps or an explicit __name__ is worth the line. Python has no operator for this on purpose — the standard library expects you to write the two calls.
- Widen it to *args and **kwargs so the inner function can take any signature, and work out why only the inner one can.
- Write compose_all(*functions) that chains any number of them, then decide whether the first or the last argument should run first.
- Give the composed function a useful __name__ built from the two originals, and see the difference it makes in a traceback.