---
title: 'Function Definition'
source: 'https://academia.sh/en/courses/python-fundamentals/function-definition'
course: 'Python Fundamentals'
language: en
updated: '2026-08-17T18:10:29+00:00'
license: 'CC BY-SA 4.0'
---

# Function Definition

A default value is evaluated once when the definition is read, not on every call: five calls make one evaluation, a mutable default accumulates five items across five calls, and four of six parameters can be given only one way.

The previous lesson measured that a loop is a protocol and showed that an exception
decides when the turns end. A loop body most often calls a function; it passes that
function different values on every turn.

The Programming Fundamentals course's function lesson built the parameter-versus-
argument distinction, the return value, and the call frame; it explained **what a
function is** and reduced argument passing to a single question — which value goes
to which parameter. In Python the answer is not one-line: a parameter can be given
only by position, only by name, or by both; surplus arguments can be collected;
parameters can be bound to a default value. One of these is not about the value but
about **timing**, and that is what this lesson measures: **when** is a default value
computed — on every call, or once?

## A Default Is an Expression

`def` is a statement, and when run it produces a function object. During that
production the body is not run — that lesson was already built — but **the
default-value expressions in the signature are run**. Their results are computed and
written onto the function object.

The meaning of this is: a default value is part of the **definition**, not the call.
It is not recomputed at call time; the object computed once when the definition is
read is **rebound** on every call. Five calls do not produce five separate defaults;
they use the same object five times.

"Once" does not mean once over the program's lifetime; it means **once every time the
definition statement runs**. A top-level definition runs once while the file is read,
so its default is computed once too. A definition written inside another function's
body, however, reruns **on every call of the outer function** and recomputes its own
default. The measured rule does not change — the default is still part of the
definition — but how many times the definition runs does.

If the value is an immutable object, this distinction is invisible. A number or a
string being the same object on every call has no consequence, because there is no
way to change it. The distinction appears once the default is **mutable**: if the
body changes it, the change carries over to the next call.

## Sentinel Value

The pattern set against this behavior is making the default `None` and producing the
real value in the body. `None` is immutable, so sharing it has no consequence; the
body builds a fresh object on every call. This role is called a **sentinel value**:
it is not used itself, it only carries the information "no argument was given."

The sentinel need not be `None`, but there is a reason it usually is. If `None` is
also among a parameter's valid values, it cannot be chosen as the sentinel — then
"not given" and "`None` was given" cannot be told apart, and a separate object is
made the sentinel instead.

## How Many Ways an Argument Can Be Given

The second measure is the number of argument forms. Python recognizes two markers in
a signature. A slash announces that the parameters to its left can be given **only by
positional argument**. A star announces that the parameters to its right can be given
**only by keyword argument**. What falls between the two can be given either way.

Two collectors are added to this. A starred parameter collects unmatched
**positional** arguments into a tuple; a double-starred parameter collects unmatched
**keyword** arguments into a dictionary. Together the two are called a **variadic**
argument, and they free the signature from an upper bound on how many arguments it
can take.

The order of these pieces in a signature is not free: first the positional-only ones,
then the ones giveable either way, then the positional collector, then the
keyword-only ones, and last the keyword collector. This order exists to make binding
**unambiguous**. Arguments at the call site are read left to right; positional ones
fill the signature's positional parameters in order, the ones they cannot fill go
into the collector, keyword ones match by name, and the ones that do not match fall
into the keyword collector. Break this order and the same call could have more than
one valid binding, with no way to tell from what was written which one gets chosen.

The same reasoning also requires parameters carrying a default to come after ones
that do not — on the positional side. No such constraint applies keyword-only: since
matching is by name there, parameters with and without a default can mix freely.

The measurement's assumptions:

- **CF16** — The oracle is the rig itself: the default expression is a function call
  and logs every evaluation of itself; the measure for "how many times was it
  evaluated" is this log.
- **CF17** — The evaluation count is read at two points: right after the definition
  statement runs, and after five calls are made. The difference between the two is
  the evaluation the calls added.
- **CF18** — Two functions do the same job and differ only in their default: one an
  empty list, the other a `None` sentinel. The rest of their bodies is identical.
- **CF19** — Sharing is shown by comparing the object two consecutive calls return
  with `is`; **no identity number is ever printed**. The object compared is a list;
  caching of small immutable values does not enter the measurement.
- **CF20** — The signature carries six parameters and two collectors; these counts
  are specific to this signature and do not carry over to another one.
- **CF21** — For calls that fail to bind, only the **exception's class** is printed,
  not its message; the message text is not part of the measurement.
- **CF22** — Where the default sits is shown by reading the field on the function
  object that carries defaults; what is read is not a memory fact, it is a field the
  language itself defines.
- **CF23** — The unpacking notation at a call site uses the same markers as the
  collectors in a signature but works in the reverse direction; the measurement
  compares the two on the same signature.

## Measurement

```python
"""When is a default value evaluated, and how do arguments bind."""

EVALUATIONS = []


def stamp(name):
    """Records every time it is called; used as a default-value expression."""
    EVALUATIONS.append(name)
    return len(EVALUATIONS)


def order(x, d=stamp("default expression")):
    return d


print(f"evaluations right after definition: {len(EVALUATIONS)}")
results = [order(i) for i in range(5)]
print(f"evaluations after five calls: {len(EVALUATIONS)}")
print(f"what five calls returned: {results}")


def accumulate(x, bucket=[]):
    bucket.append(x)
    return bucket


def sentinel(x, bucket=None):
    if bucket is None:
        bucket = []
    bucket.append(x)
    return bucket


print()
print(f"{'call':>6s} {'bucket=[] length':>17s} {'bucket=None length':>19s}")
previous_a = previous_b = None
for i in range(1, 6):
    a, b = accumulate(i), sentinel(i)
    print(f"  {i:4d} {len(a):17d} {len(b):19d}")
    if i > 1:
        same_a, same_b = a is previous_a, b is previous_b
    previous_a, previous_b = a, b
print(f"is two consecutive calls' bucket the same object: "
      f"bucket=[] {same_a}, bucket=None {same_b}")
print(f"the default sitting on the function object: "
      f"accumulate {accumulate.__defaults__}, sentinel {sentinel.__defaults__}")
print(f"is the returned list the very default sitting on the function: "
      f"{accumulate(6) is accumulate.__defaults__[0]}")


def register(a, b, /, c, d=40, *extra, e, f=60, **rest):
    """a,b positional-only; e,f keyword-only; extra and rest collect the surplus."""
    return {"a": a, "b": b, "c": c, "d": d, "extra": extra, "e": e, "f": f,
            "rest": rest}


CALLS = (
    "register(1, 2, 3, e=5)",
    "register(1, 2, c=3, e=5)",
    "register(1, 2, 3, 4, 7, 8, e=5, f=9, g=0)",
    "register(1, 2, 3)",
    "register(a=1, b=2, c=3, e=5)",
)

print()
print(f"{'call':<44s} binding")
for text in CALLS:
    try:
        result = eval(text)
        bound = (f"a={result['a']} b={result['b']} c={result['c']} d={result['d']} "
                 f"extra={result['extra']} e={result['e']} f={result['f']} rest={result['rest']}")
    except TypeError as error:
        bound = type(error).__name__
    print(f"  {text:<42s} {bound}")

POSITIONAL = (1, 2, 3)
KEYWORD = {"e": 5, "g": 0}
unpacked = register(*POSITIONAL, **KEYWORD)
print(f"  {'register(*POSITIONAL, **KEYWORD)':<42s} "
      f"a={unpacked['a']} b={unpacked['b']} c={unpacked['c']} d={unpacked['d']} "
      f"extra={unpacked['extra']} e={unpacked['e']} f={unpacked['f']} "
      f"rest={unpacked['rest']}")

print()
print("parameter  positional-giveable  keyword-giveable")
for name, positional, keyword in (("a", True, False), ("b", True, False),
                                ("c", True, True), ("d", True, True),
                                ("e", False, True), ("f", False, True)):
    print(f"  {name:<9s} {('yes' if positional else 'no'):>17s}"
          f" {('yes' if keyword else 'no'):>18s}")
```

```
evaluations right after definition: 1
evaluations after five calls: 1
what five calls returned: [1, 1, 1, 1, 1]

  call  bucket=[] length  bucket=None length
     1                 1                   1
     2                 2                   1
     3                 3                   1
     4                 4                   1
     5                 5                   1
is two consecutive calls' bucket the same object: bucket=[] True, bucket=None False
the default sitting on the function object: accumulate ([1, 2, 3, 4, 5],), sentinel (None,)
is the returned list the very default sitting on the function: True

call                                         binding
  register(1, 2, 3, e=5)                     a=1 b=2 c=3 d=40 extra=() e=5 f=60 rest={}
  register(1, 2, c=3, e=5)                   a=1 b=2 c=3 d=40 extra=() e=5 f=60 rest={}
  register(1, 2, 3, 4, 7, 8, e=5, f=9, g=0)  a=1 b=2 c=3 d=4 extra=(7, 8) e=5 f=9 rest={'g': 0}
  register(1, 2, 3)                          TypeError
  register(a=1, b=2, c=3, e=5)               TypeError
  register(*POSITIONAL, **KEYWORD)           a=1 b=2 c=3 d=40 extra=() e=5 f=60 rest={'g': 0}

parameter  positional-giveable  keyword-giveable
  a                       yes                 no
  b                       yes                 no
  c                       yes                yes
  d                       yes                yes
  e                        no                yes
  f                        no                yes
```

## An Expression Evaluated Once

The first three lines close the timing question. Right after the definition
statement runs, **before any call is made yet**, the evaluation count is **1**. The
default expression ran while the definition was read.

After five calls the count is still **1**. The calls added no evaluation. The third
line shows this directly: all five calls return the same value — every one of them
sees the single object computed at definition time.

There is **no link** between how many times a function is called and how many times
its default is evaluated. This is why it is wrong to read an expression written in
the signature as if it were written in the body: one runs once, the other on every
call.

## The Shared Bucket

The middle table makes this single evaluation pay its cost. In the left column the
default is an empty list; over five calls the length grows **1, 2, 3, 4, 5**. Every
call finds the list the previous one left behind.

In the right column, the sentinel pattern; the length stays **1** across all five
calls. Every call builds its own list and inherits nothing from the one before.

The bottom line states the reason through identity. The object two consecutive calls
return in the `bucket=[]` form comes out **true** under the `is` test: the same
object. In the sentinel pattern, **false**: separate objects. This is where the
assumption that a function gets "a new list" on every call breaks — the list was
built once at definition time, bound to the function object, and from that moment on
lives as a single object that every call mutates.

The second-to-last line shows **where** the default sits, and this is the lesson's
most direct proof. When the field on the function object carrying defaults is read,
it holds `[1, 2, 3, 4, 5]` — the list five calls accumulated, sitting, after the
calls are done, **on the function itself**. In the sentinel pattern the same field
holds only `None`; nothing accumulates there, because the accumulating lists were
built in the body and left behind with the call.

The last line closes the loop: a call's returned list and the default sitting on the
function object come out **true** under the `is` test. The two are a single object.
The sentence "the default is part of the definition" stops being a comment here; it
becomes something readable off the function itself.

The result itself is not a bug, it is the rule's direct consequence. The Programming
Fundamentals course wrote this trap as a warning; the numbers here give the reason
for it. For someone who knows the rule, the behavior is not surprising: if the body
changes the default, what it changes **is part of the definition**.

## Six Parameters, Two Markers

The bottom two tables measure binding. The first two calls give the same result: `c`
is given once by position, once by name, and the binding is exactly the same. Because
`d`, `e`, and `f` are not given, `d` and `f` fall to their defaults; `e` has to be
written at the call site regardless, because it has no default and can only be given
by keyword.

The third call fills both collectors. Six positional arguments are given; the first
four satisfy `a`, `b`, `c`, `d`, and the surplus **two** go into the `extra` tuple.
On the keyword side, `e` and `f` find their own parameters, and `g`, which has no
match in the signature, falls into the `rest` dictionary. Collectors are parameters
that **collect** an argument that cannot bind, instead of turning it into an error.

The last two calls fail to bind, and both give `TypeError`. The fourth is missing
`e`: a call cannot be built without a keyword-only parameter that has no default. The
fifth has the opposite problem — `a` and `b` are given by name, but sit left of the
slash and can only be taken by position. Given by name, they are seen not as
satisfying the parameters but as names that would fall into `rest`, leaving the two
positional parameters empty.

The sixth line shows what the same markers do **at the call site**. While a
signature **collects** with the star markers, a call does the exact opposite: it
**unpacks** a tuple into positional arguments and a dictionary into keyword
arguments. The three-item tuple satisfies `a`, `b`, `c`; `e` in the dictionary finds
its own parameter, and `g` falls into the `rest` dictionary.

That the two directions use the same markers is not a coincidence: collecting and
unpacking are each other's inverse, and passing a function's received arguments on to
another function unchanged means using both side by side. This is the basic pattern
for a wrapping function — it collects arguments without knowing the signature and
unpacks them into the function it wraps.

The last table summarizes the whole signature. **Four of the six** parameters can be
given only one way — `a` and `b` only by position, `e` and `f` only by name. Only
**two** accept both ways. This is what the markers do: they narrow how many ways a
parameter can be given. The payoff of narrowing is that the call notation does not
change along with the signature — when a parameter kept positional-only is renamed,
no call breaks, and the same holds when a parameter kept keyword-only changes order.

## Summary

- A default-value expression is part of the **definition**, not the call: it is
  evaluated once while the definition is read, and five calls do not change that
  count — there is no link between call count and evaluation count.
- This is invisible with an immutable default; with a **mutable** one, the body
  changes part of the definition, and five calls accumulate **five items** in a
  single list.
- The `None`-sentinel pattern builds a fresh object in the body; the bucket of two
  consecutive calls comes out as separate objects under the `is` test, and the
  length stays **1** on every call.
- A slash makes the parameters to its left positional-only, a star makes the ones to
  its right keyword-only; in the measured signature, **four of six** parameters can
  be given only one way, two either way.
- Starred and double-starred collectors gather unmatched positional and keyword
  arguments; the same markers work in reverse at a call site, unpacking a tuple and
  a dictionary into arguments. An argument that cannot be collected and a parameter
  left unfilled both produce `TypeError`.
- The default sits in the function object's own field: after five calls, the entire
  list accumulated in that field can be read, and the returned list comes out as the
  same object under the `is` test.

## Next Step

A function body uses names beyond the ones arriving with arguments: ones built in the
body, ones coming from the definition that encloses it, ones sitting at the outermost
level of the file, and ones the language itself supplies. The Programming
Fundamentals course built this search order as a language-independent concept. In
Python the order has **four levels**, and what decides which level a name is found in
is not where it is **read**, but where it is **assigned**. The next lesson defines the
same name at all four levels at once and measures which one wins — and how adding a
single assignment line to a body moves that same name to a different level.
