Role of CO_VARARGS in Python Bytecode Execution

The CO_VARARGS flag is a compiler-level bit flag stored in a Python code object’s co_flags attribute that signifies a function accepts arbitrary positional arguments via the *args syntax. During bytecode execution, this flag instructs Python's runtime evaluation loop and argument-binding routines to collect any positional arguments that exceed the explicitly declared positional parameters, package them into a single tuple, and bind that tuple to a dedicated local variable slot in the execution frame.

Flag Definition and Compilation

When Python compiles source code into a code object (PyCodeObject), the compiler analyzes the function signature. If the signature includes a variable positional parameter (such as *args), the compiler sets the CO_VARARGS bit (internally defined as 0x0004 in CPython’s code.h) within co_flags.

You can inspect this flag directly using the inspect module:

import inspect

def example_func(a, *args):
    pass

# CO_VARARGS corresponds to inspect.CO_VARARGS (value 4)
has_varargs = bool(example_func.__code__.co_flags & inspect.CO_VARARGS)

Argument Binding During Frame Initialization

Before the CPython virtual machine begins executing the bytecode instructions of a function frame, it must resolve incoming arguments. This is handled by internal evaluation functions (such as _PyEval_Vector or _PyFunction_Vectorcall).

The execution sequence proceeds as follows:

  1. Positional Parameter Mapping: The interpreter first maps supplied positional arguments to the function's explicit positional parameter slots.
  2. Excess Argument Detection: If the caller provides more positional arguments than the number of positional parameters defined by co_argcount, the interpreter checks co_flags & CO_VARARGS.
  3. Tuple Allocation:
    • If CO_VARARGS is set, the interpreter creates a new PyTuple object containing the remaining positional arguments.
    • If CO_VARARGS is not set, the interpreter raises a TypeError indicating that the function was given more positional arguments than it accepts.
  4. Empty Fallback: If the function defines *args but no excess positional arguments were supplied, the runtime assigns an empty tuple () to the argument slot.

Local Variable Assignment and Bytecode Access

Once the tuple is constructed, the runtime places the reference to this tuple directly into the execution frame's array of local variables (fastlocals). The designated index in this array is immediately after the standard positional and keyword-only parameter slots.

Inside the function body, bytecode does not require a special instruction to read *args. The compiler emits a standard LOAD_FAST instruction referencing the local variable slot assigned to args. Because the packing process occurs entirely during the C-level function call preamble, the bytecode execution loop treats the variable positional argument container as an ordinary local tuple.