Functions, Parameters, Return Values & Calling Conventions
In Python, functions are first-class objects created at runtime by the def statement. Understanding CPythonβs argument binding protocol, frame value stack execution, positional-only (/) and keyword-only (*) parameters, and the mutable default parameter initialization trap is essential for writing robust, high-performance software.
This chapter details function object architecture (PyFunctionObject), CPython call stack mechanics, positional-only/keyword-only parameter syntax (PEP 570), and default argument instantiation.
1. Function Object Architecture (PyFunctionObject)
When Python executes a def statement, it compiles the function body into a PyCodeObject and wraps it in a PyFunctionObject struct on the heap:
CPython Function Struct (PyFunctionObject):
[ PyFunctionObject @ 0x7F9A ]
βββ func_code --> [ PyCodeObject ] (Bytecode, variable names, constants)
βββ func_globals --> Pointer to module globals dict (for LEGB lookup)
βββ func_defaults --> Tuple of default argument values (evaluated ONCE at def time!)
βββ func_kwdefaults--> Dict of keyword-only default values
βββ func_closure --> Tuple of PyCellObject refs (for captured free variables)
βββ func_annotations--> Dict of type annotationsBecause func_defaults is attached directly to the PyFunctionObject struct when the def statement is evaluated, default argument values persist across all subsequent function calls.
2. Parameter Calling Conventions: Positional-Only (/) & Keyword-Only (*)
Python (PEP 570 & PEP 3102) allows explicit control over parameter binding syntax:
def configure_service(host, port, /, timeout=30, *, use_ssl=True):
passParameter Syntax Rules:
def func(pos_only, /, standard_param, *, kw_only):
βββββββββ ββββββββββββββ ββββββββ
Positional Positional or Keyword Only
Only Keyword (Must pass as name=val)- Positional-Only Parameters (
/): Parameters left of/cannot be passed as keyword arguments. This allows changing parameter names in library code without breaking caller code. - Keyword-Only Parameters (
*): Parameters right of*must be passed as explicit keyword arguments (use_ssl=True). This prevents accidental parameter positional displacement bugs.
3. The Mutable Default Argument Trap
The most famous trap in Python stems from func_defaults being evaluated once at module load time:
# THE TRAP: Default list instantiated ONCE during 'def' evaluation!
def append_to_list(element, target_list=[]):
target_list.append(element)
return target_list
print(append_to_list(1)) # [1]
print(append_to_list(2)) # [1, 2] (target_list reused the SAME list instance!)Production Fix:
Always use None as the default marker for mutable parameters and instantiate a new container inside the function body.
4. Production Trade-offs & Frame Overhead
*argsand**kwargsOverhead:*argspacks extra positional arguments into a tuple;**kwargspacks keyword arguments into a dictionary. While flexible, packing and unpacking args adds a minor overhead. Use explicit parameters for hot-path public APIs.- Function Call Cost: Function calls in Python involve creating a
PyFrameObjecton the C stack. For micro-benchmarks involving simple math operations inside hot loops, inlining simple logic avoids frame allocation overhead.