skip to content

questions

4

Why do two functions from the same def share a code object but not defaults?

level: middleimportance: must knowfreq 40%

answer

  1. Two moments, not one
  2. Compiled once; defined possibly many times
  3. The shared half must be immutable
  4. A default can be any runtime expression
  5. Look for the tuple attribute on the function

basics

~20 s

The code object is compiled once and is immutable, so it can be shared. Default values are ordinary expressions evaluated each time the def statement executes, so they belong to the function object created then, in defaults and kwdefaults.

solid answer

~40 s

Compilation and function creation happen at different times. The compiler turns the body into one immutable **code object**; executing the `def` statement later builds a **function object** that points at it. A default value is an arbitrary expression — `x=[]`, `x=compute()`, `x=factor` — evaluated in the enclosing scope *at that moment*, so it cannot be baked into a constant table shared by every function from that `def`; it is stored on the function, in `__defaults__` for positional parameters and `__kwdefaults__` for keyword-only ones. That is exactly why `def f(items=[])` reuses one list forever: the list was built once, when the `def` ran, and the function keeps a reference to it. `__globals__` and `__closure__` are on the function for the same reason.

code

python · 8 lines
python
def make_scaler(factor):
    def scale(value, factor=factor):
        return value * factor
    return scale

double, triple = make_scaler(2), make_scaler(3)
print(double.__code__ is triple.__code__)
print(double.__defaults__, triple.__defaults__)

go deeper

for a junior

Remember the headline: the body is compiled once, but a default value is created when the def runs, which is why a mutable default is reused across calls.

for a middle

Explain the mechanics — compile time versus definition time, the immutable shared code object, and __defaults__/__kwdefaults__ on the function — and demonstrate it with a factory that yields two functions with one code object.

for a senior

Use the split to reason about real code: decorators and wrappers that must carry defaults across, functions rebound between modules keeping their original __globals__, and when rewriting __defaults__ in a test helper is acceptable.

for a principal

Own the convention: sentinel-and-fresh-object as the house rule for mutable defaults, and a clear position on how much runtime function surgery a codebase tolerates before it becomes unreviewable.

## Compile time versus definition time The confusion this question targets comes from collapsing two separate moments into one. **Compile time** is when CPython turns source text into bytecode. Every `def` in the source yields exactly one immutable code object, holding the body's bytecode plus static metadata (`co_argcount`, `co_varnames`, `co_names`, `co_consts`, `co_flags`). **Definition time** is when the `def` statement is *executed* — when the module is imported, or every time the enclosing factory function is called. At that instant CPython builds a **function object**: a small record binding the already-compiled code object to everything that could only be known now. A default value falls squarely on the second side of that line. `def f(x=[])`, `def f(x=compute())` and `def f(x=factor)` are all legal, and Python cannot precompute any of them at compile time — the first allocates a mutable object, the second calls a function, the third reads a name from the enclosing scope. So the compiler emits code that *evaluates the default expressions* as part of executing the `def`, and stores the resulting values on the function object. ```python def make_scaler(factor): def scale(value, factor=factor): return value * factor return scale double, triple = make_scaler(2), make_scaler(3) print(double.__code__ is triple.__code__) # True print(double.__defaults__, triple.__defaults__) # (2,) (3,) ``` One compiled body, two function objects, two different default tuples. ## Where each piece lives On the **code object** (static, immutable, shared): bytecode, `co_name`, `co_filename`, `co_firstlineno`, argument *counts*, local and global *names*, folded constants, flags. On the **function object** (per definition): `__defaults__` (a tuple, one entry per positional parameter that has a default, right-aligned), `__kwdefaults__` (a dict for keyword-only parameters), `__globals__` (the module namespace this function resolves globals through), `__closure__`, `__name__`, `__qualname__`, `__doc__`, `__module__`, `__dict__`, and since 3.14 the deferred `__annotate__` used to produce `__annotations__` on demand. Both `__defaults__` and `__kwdefaults__` are writable, which is a legitimate way to re-arm a function without redefining it: ```python def collect(items=[]): items.append(1) return items print(collect()) # [1] print(collect()) # [1, 1] - one shared list collect.__defaults__ = ([],) print(collect()) # [1] - re-armed with a fresh list ``` ## The consequence everyone meets first Because the default object is created once and held by the function, a mutable default accumulates across calls. The habitual fix is a `None` sentinel and a fresh object inside the body. The deeper point for an interview is the *reason*: the value could not have been stored on the shared, immutable code object, because two functions compiled from the same `def` may legitimately need different defaults — as `make_scaler` above shows. The same reasoning explains a family of related facts. `__globals__` is per function, so a function moved between modules by assignment still resolves globals in the module it was *defined* in. Class bodies executed twice produce methods sharing code objects but with separate function objects, which is what lets a decorator applied at definition time attach per-function state. And `types.FunctionType(code, globals, name, defaults)` makes the split explicit: you hand it a code object plus exactly the per-definition pieces. ```python import types def greet(name="world"): return f"hi {name}" clone = types.FunctionType(greet.__code__, greet.__globals__, "clone", ("there",)) print(clone(), greet()) # hi there hi world ``` Same compiled body; a different default supplied at construction. ## The same split, seen from a decorator A decorator runs at definition time, which is exactly why it can see and adjust the per-definition pieces: it receives the freshly built function object and can read `__name__`, `__doc__`, `__defaults__` and `__dict__` off it. What it cannot touch is the compiled body — that code object is fixed and shared. A wrapper is therefore a *second* function object with its own defaults and its own `__globals__`, which is why `functools.wraps` exists at all: without it the wrapper advertises its own metadata rather than the wrapped function's. Class bodies show the split too. Every method is a function object built when the class body executes, over a code object compiled once. Defining the same class twice — a class created inside a factory, a module imported under two names — produces new function objects sharing the old compiled bodies, and each of those function objects carries its own defaults. ## Answering it well Say the two moments out loud — compiled once, defined possibly many times — then name where the pieces live, then give one consequence. Avoid the two common wrong turns: claiming defaults are re-evaluated on every *call* (they are not; that would make the mutable-default behaviour impossible), and claiming each `def` execution recompiles the body (it does not; only the function object is new).

  • Where do defaults for keyword-only parameters go?
    Into `__kwdefaults__`, a dict keyed by parameter name, rather than the positional `__defaults__` tuple. `__defaults__` is right-aligned against the positional parameters, so a function with three positional parameters and one default has a one-element tuple that applies to the last parameter.
  • Are default expressions re-evaluated on each call?
    No — only once, when the `def` statement executes. If they were re-evaluated per call, the classic mutable-default accumulation could not happen. Each call simply reads the stored object out of `__defaults__` or `__kwdefaults__` and binds it to the parameter.
  • Can you change a function's defaults without redefining it?
    Yes. `__defaults__` and `__kwdefaults__` are writable, so `f.__defaults__ = ([],)` re-arms a function with a fresh object. It is occasionally handy in test setup or a patching helper, but as production code it hides behaviour from anyone reading the `def`, so a sentinel parameter is almost always the better answer.

The compiled body is a recipe card in a shared binder; each function object is a cook who took a photocopy reference plus their own pre-measured ingredients. The card never changes, but every cook brought their own bowl — and if two cooks share one bowl, it stays dirty between services.

saying these in an interview costs you the question

  • Says default expressions are evaluated on every call
  • Thinks each def execution recompiles the body
  • Puts default values in the code object's co_consts
  • Cannot name __defaults__ or __kwdefaults__
  • Believes two factory-made functions have separate bytecode

context

open as a page

What does a Python function's __code__ attribute give you?

level: juniorimportance: should knowfreq 28%

basics

~20 s

A function's code is its code object: the compiled bytecode for the body plus the static facts the compiler worked out — name, filename, argument count, local names, constants and flags. It is immutable, and every function built from the same def shares it.

open as a page

On a Python code object, how do co_varnames and co_names differ?

level: middleimportance: should knowfreq 30%

basics

~20 s

co_varnames lists the function's local variable names, parameters first, which the compiler turned into fixed numbered slots. co_names lists names that must be resolved at run time by name — globals, builtins, attributes and imported modules. Constants live in a third tuple, co_consts.

open as a page

Which expressions does CPython's compiler fold into co_consts before the code runs?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Only expressions built entirely from literals of immutable types, and only up to a size cap: 60 * 60 * 24 becomes the constant 86400. Anything touching a name, an attribute or a call is left to run time, so hoisting those yourself is what actually pays.

open as a page