Skip to content

31. Function Calls

CALL opcode mechanics, positional and keyword argument handling, *args/**kwargs unpacking, and call overhead.

Function calls are one of the most important execution paths in CPython. A call connects several systems at once: bytecode execution, frames, argument binding, descriptors, methods, closures, the C API, reference counting, exceptions, and return handling.

A simple call:

result = f(1, 2)

looks small at the source level. At runtime, CPython must:

load the callable
load the arguments
choose the correct call protocol
bind arguments to parameters
create or initialize a frame if calling Python code
execute the callee
return a result or propagate an exception
store the result

The evaluation loop sees a function call as a bytecode operation that consumes callable state and arguments from the frame stack, then pushes a result.

31.1 What a Call Means

In Python, a call expression has this general form:

callable_object(arguments)

The object before the parentheses must be callable.

Examples:

f()
len(xs)
obj.method(1)
C(10)
decorator(fn)
callback(event)

Many kinds of objects can be callable:

Callable kindExampleRuntime behavior
Python functionf()Creates or initializes a Python frame
Built-in functionlen(xs)Calls C implementation
Bound methodobj.method()Calls function with bound self
ClassC()Calls metaclass construction path
Callable instanceobj()Calls obj.__call__
C extension functionmod.func()Calls native extension code
Coroutine functionasync_fn()Creates coroutine object
Generator functiongen()Creates generator object
Descriptor resultobj.attr()May bind before call

The syntax is uniform. The runtime path depends on the callable object’s type.

31.2 Call Bytecode

A call compiles into bytecode that loads a callable and its arguments, then executes a call instruction.

For:

def g(a, b):
    return f(a, b)

Conceptually:

LOAD_GLOBAL f
LOAD_FAST a
LOAD_FAST b
CALL 2
RETURN_VALUE

The stack before the call contains the callable and arguments:

[f, a, b]

The call instruction consumes them and pushes the return value:

[result]

The exact instruction sequence varies by Python version. Modern CPython has changed the call protocol several times to improve performance. The stable concept is:

prepare callable and arguments
perform call
push result or raise exception

31.3 The Call Stack Layout

A call instruction needs a stack layout. A simplified layout for positional calls is:

callable
arg0
arg1
arg2
...

For:

f(x, y, z)

the stack before the call is conceptually:

[f, x, y, z]

After the call:

[result]

Keyword calls need additional metadata.

f(x, y=10)

Conceptually, CPython must represent:

callable = f
positional args = [x]
keyword names = ["y"]
keyword values = [10]

Modern CPython tries to represent this without building temporary tuples and dictionaries when possible.

31.4 Argument Evaluation Order

Python evaluates call components in a defined order.

For:

f(a(), b(), c())

the calls happen left to right:

evaluate f
evaluate a()
evaluate b()
evaluate c()
call f with the three results

If b() raises, c() does not run.

This matters because argument expressions can have side effects:

f(print("a"), print("b"))

The evaluation loop must preserve Python’s source-level order while using the stack for temporary values.

31.5 Positional Arguments

A simple Python function:

def add(a, b):
    return a + b

has two positional parameters.

Calling:

add(2, 3)

binds:

a = 2
b = 3

In CPython, the callee frame uses fast local slots.

Conceptually:

callee frame localsplus:
    slot 0: a = 2
    slot 1: b = 3

Then the bytecode executes:

LOAD_FAST a
LOAD_FAST b
BINARY_OP +
RETURN_VALUE

The binding step is a major part of call overhead.

31.6 Keyword Arguments

Keyword arguments bind by name.

def area(width, height):
    return width * height

area(height=10, width=20)

The arguments arrive out of parameter order, but the callee slots must be filled correctly:

width  = 20
height = 10

CPython must check:

whether each keyword matches a parameter
whether the same parameter was supplied twice
whether required parameters are missing
whether unexpected keywords should be rejected or collected by **kwargs

Example error:

area(20, width=10)

This supplies width twice: once positionally and once by keyword.

31.7 Default Arguments

Default arguments are stored on the function object, not recreated on every call.

def f(x, step=1):
    return x + step

The function object stores the default value for step.

Conceptually:

function f
    code object
    globals
    defaults: (1,)

When called as:

f(10)

CPython fills:

x = 10
step = 1

The famous mutable default behavior follows from this:

def append_item(x, xs=[]):
    xs.append(x)
    return xs

The list object is stored in the function defaults and reused across calls.

31.8 Keyword-Only Arguments

Keyword-only parameters must be supplied by keyword.

def connect(host, *, timeout, retries=3):
    ...

Valid:

connect("example.com", timeout=10)

Invalid:

connect("example.com", 10)

The function code object stores counts and layout metadata. During argument binding, CPython uses this metadata to decide which fast local slots receive values.

Conceptually:

positional slots
keyword-only slots
*args slot, if present
**kwargs slot, if present

31.9 Variadic Positional Arguments

A *args parameter collects extra positional arguments.

def f(a, *args):
    return args

Call:

f(1, 2, 3)

Binding:

a = 1
args = (2, 3)

CPython creates a tuple for the extra positional arguments. If no extra arguments are passed, it uses an empty tuple.

The *args slot is still an ordinary local variable slot from the bytecode’s perspective.

31.10 Variadic Keyword Arguments

A **kwargs parameter collects extra keyword arguments.

def f(a, **kwargs):
    return kwargs

Call:

f(1, x=2, y=3)

Binding:

a = 1
kwargs = {"x": 2, "y": 3}

CPython creates a dictionary for unmatched keyword arguments.

If the function has no **kwargs, unexpected keyword arguments are errors.

31.11 Starred Call Arguments

A call can unpack positional arguments:

args = (1, 2)
f(*args)

CPython must evaluate args, iterate or convert it to positional arguments, then merge it into the call.

Multiple unpackings are allowed:

f(0, *xs, *ys)

The runtime must preserve left-to-right ordering.

Conceptually:

positional arguments =
    [0] + list(xs) + list(ys)

If an unpacked object is not iterable, CPython raises TypeError.

31.12 Double-Starred Call Arguments

Keyword arguments can be unpacked from mappings:

kwargs = {"x": 1, "y": 2}
f(**kwargs)

Multiple mappings can be supplied:

f(**a, **b)

CPython must check that keys are strings when calling Python functions in the usual way, and must detect duplicate keyword assignments.

Example:

f(x=1, **{"x": 2})

This is an error because x is supplied twice.

31.13 Argument Binding Errors

Many call failures happen before the function body starts.

Examples:

def f(a, b):
    pass

f(1)
f(1, 2, 3)
f(a=1, c=2)
f(1, a=2)

These raise TypeError.

The evaluation loop initiates the call, but the call machinery performs binding checks.

A call can fail at several phases:

callable lookup fails
argument expression raises
argument unpacking fails
object is not callable
argument binding fails
callee body raises
return handling fails indirectly through cleanup

31.14 Python Function Calls

For a normal Python function call, CPython prepares a new frame execution state.

def f(a, b):
    c = a + b
    return c

Call:

f(2, 3)

Conceptually:

function object:
    code object
    globals
    defaults
    closure

call:
    bind a = 2
    bind b = 3
    allocate or initialize frame
    run evaluation loop
    return result

The new frame points to the same code object every call, but local slots differ for each call.

31.15 Built-in Function Calls

Built-in functions are implemented in C.

len(xs)

The call does not create a Python frame for the body of len. Instead, CPython calls a C function.

Conceptually:

LOAD_GLOBAL len
LOAD_FAST xs
CALL 1
    call C implementation of len
push result

The C implementation still receives Python objects and returns a Python object.

For len(xs), it may call the object’s length slot and return a Python integer.

Built-ins are fast partly because they avoid Python bytecode execution for their body.

31.16 C Extension Function Calls

C extension functions follow similar principles to built-ins.

A C extension function receives arguments as Python objects, validates them, performs native work, and returns a Python object or signals an exception.

Conceptual C shape:

static PyObject *
mod_func(PyObject *self, PyObject *args)
{
    int x;

    if (!PyArg_ParseTuple(args, "i", &x)) {
        return NULL;
    }

    return PyLong_FromLong(x + 1);
}

Modern extension functions may use faster calling conventions that avoid packing arguments into a tuple.

Regardless of convention, the rule is:

return PyObject* on success
return NULL with exception set on failure

The evaluation loop checks the result.

31.17 Vectorcall

Vectorcall is CPython’s fast calling convention for many callable types.

Its purpose is to pass arguments as a C array of PyObject * values rather than always building temporary tuple and dict objects.

Conceptually:

old-style call:
    build args tuple
    build kwargs dict
    call function

vectorcall-style:
    pass pointer to argument array
    pass argument count
    pass keyword names separately
    call function

This reduces allocation and copying on hot call paths.

For a call like:

f(a, b, c)

the arguments may already be contiguous on the interpreter stack. Vectorcall lets CPython pass that array-like region directly to the callable implementation.

31.18 Calls and Bound Methods

Method calls need binding.

obj.method(10)

At the language level, this means:

look up method on obj
bind obj as self
call method with self and 10

If method is a function defined on the class, accessing it through an instance creates a bound method conceptually:

bound = obj.method
bound(10)

The bound method carries:

function
self object

Then calling it supplies self automatically.

CPython optimizes common method calls to avoid creating a temporary bound method object when the method is immediately called.

31.19 Descriptor Binding

Method calls are based on the descriptor protocol.

A function stored on a class is a descriptor. When accessed through an instance, its __get__ behavior produces a bound method.

Example:

class C:
    def f(self, x):
        return x

obj = C()
obj.f(10)

Conceptually:

C.__dict__["f"].__get__(obj, C)
    returns bound method
bound method called with x = 10

This is why:

C.f(obj, 10)

also works. In that form, no automatic instance binding happens through an instance attribute access. You pass obj explicitly.

31.20 Class Calls

Calling a class creates or initializes an instance.

obj = C(1, 2)

A class is callable because its metaclass is callable. Usually the metaclass is type.

Conceptual flow:

C.__call__(*args, **kwargs)
    calls C.__new__(C, *args, **kwargs)
    if result is instance of C:
        calls result.__init__(*args, **kwargs)
    returns result

Example:

class C:
    def __new__(cls, value):
        obj = super().__new__(cls)
        return obj

    def __init__(self, value):
        self.value = value

The call C(10) is not merely allocation. It is a protocol involving __new__, __init__, and metaclass behavior.

31.21 Callable Instances

An object can be callable if its type defines __call__.

class Adder:
    def __init__(self, n):
        self.n = n

    def __call__(self, x):
        return self.n + x

add10 = Adder(10)
print(add10(5))

The call:

add10(5)

conceptually invokes:

type(add10).__call__(add10, 5)

At the CPython level, the object’s type provides call behavior through type slots.

31.22 Decorators and Calls

Decorators are executed at function definition time.

@decorator
def f():
    pass

Conceptually:

def f():
    pass

f = decorator(f)

The decorator call happens while executing the containing module, class, or function body.

Multiple decorators:

@d1
@d2
def f():
    pass

mean:

f = d1(d2(f))

This matters because decorators are ordinary calls. They can return any callable or non-callable object.

31.23 Calls and Closures

A closure call carries captured variables through cell objects.

def outer(x):
    def inner(y):
        return x + y
    return inner

f = outer(10)
f(5)

The function inner has:

code object
globals
closure tuple containing cell for x

When called, its frame can access x through the closure cell.

Conceptually:

inner frame:
    local y = 5
    free variable x -> cell -> 10

The call path sets up normal arguments and also makes closure cells visible to LOAD_DEREF.

31.24 Calls and Generators

Calling a generator function does not run its body immediately.

def gen():
    yield 1

g = gen()

The call creates a generator object.

The function body starts when the generator is resumed:

next(g)

Conceptually:

call generator function:
    create generator object with suspended frame
    return generator

next(generator):
    resume frame
    execute until yield or return

This is a major difference from ordinary function calls.

31.25 Calls and Coroutines

Calling an async function creates a coroutine object.

async def fetch():
    return 1

coro = fetch()

The body usually does not run to completion at call time. It runs when awaited or scheduled.

result = await coro

Conceptually:

call async function:
    create coroutine object
    return coroutine

await coroutine:
    execute or resume coroutine frame

The function call creates the execution object. Await drives execution.

31.26 Recursive Calls

Recursive calls create multiple active frames for the same code object.

def fact(n):
    if n <= 1:
        return 1
    return n * fact(n - 1)

For fact(4):

fact frame: n = 4
    fact frame: n = 3
        fact frame: n = 2
            fact frame: n = 1

Each frame has separate fast locals and stack state.

CPython checks recursion depth to prevent uncontrolled recursion from exhausting runtime resources.

31.27 Calls and Exceptions

A call can exit by returning or by raising.

def f():
    raise ValueError("bad")

def g():
    return f()

When f raises, it does not push a normal return value. The exception propagates through the call stack.

Conceptually:

frame g calls frame f
frame f raises ValueError
frame f unwinds
frame g receives exception instead of return value
frame g unwinds unless it handles the exception

If g has a handler:

def g():
    try:
        return f()
    except ValueError:
        return 0

the exception is caught in g and normal execution resumes at the handler.

31.28 Return Values

Every Python function returns a value.

If no explicit return value exists, it returns None.

def f():
    pass

Conceptually:

LOAD_CONST None
RETURN_VALUE

A call expression always expects either:

a returned Python object
or an exception

There is no third normal outcome.

31.29 Reference Ownership During Calls

Calls are reference-sensitive.

During a call, CPython must keep alive:

callable object
argument objects
keyword name objects
temporary bound method state
callee frame
return value
exception objects, if any

The call instruction must release temporary references after the call succeeds or fails.

A simplified call path:

result = PyObject_Call(callable, args, kwargs);
if (result == NULL) {
    goto error;
}
push(result);

But before and after this, the interpreter must clean up argument references and stack entries correctly.

Bad reference handling in a call path can leak arguments or destroy objects still in use.

31.30 Calls Can Reenter Python

A call inside one frame can run arbitrary Python code.

This is obvious for direct Python function calls, but also true for operations that do not look like calls.

Example:

obj.x

can call obj.__getattribute__.

Example:

a + b

can call a.__add__.

Example:

for x in xs:
    ...

can call iterator methods.

So the interpreter must assume that many operations can reenter Python execution.

A call stack can grow from explicit or implicit calls:

frame A
    executes LOAD_ATTR
        calls Python descriptor
            frame B

31.31 Method Call Optimization

This source pattern is common:

obj.method(arg)

A naive implementation would:

load obj
load method attribute
create bound method object
load arg
call bound method
destroy bound method object

CPython optimizes this by recognizing common method-call patterns.

The optimized path avoids allocating a temporary bound method when possible:

load method function and self separately
call function with self and arguments

This reduces allocation and reference count traffic.

The language behavior is unchanged. The optimization only changes the internal call path.

31.32 Calls and Inline Caches

Call bytecode can use inline caches.

A call site often sees the same callable repeatedly:

for x in xs:
    total += f(x)

The call instruction at this source location may repeatedly call the same function f.

CPython can cache information such as:

callable type
function version
argument shape
method lookup result
global lookup version
descriptor result

If guards pass, the interpreter can use a fast path.

If the callable changes, it falls back to generic behavior.

31.33 Calls and Specialization

Specialization can optimize call-heavy code.

Example:

def loop(xs):
    total = 0
    for x in xs:
        total += int(x)
    return total

The call site for int(x) may become specialized if it repeatedly sees the same callable and argument shape.

Specialization does not change Python semantics. It only accelerates common cases under guards.

The stack contract remains:

before call: callable and arguments
after call: result
or exception

31.34 Calls and the GIL

In traditional CPython, Python bytecode execution occurs while holding the GIL.

A call to Python code continues within that model.

A call to C code may release the GIL if the C implementation chooses to do so around long-running work.

Examples where C code may release the GIL:

blocking I/O
compression
hashing
numeric kernels
database drivers
system calls

This means a call may temporarily allow another Python thread to run if the callee is native code that releases the GIL.

For pure Python calls, bytecode execution remains serialized by the GIL in traditional builds.

31.35 Calls and Builtin Methods

Builtin methods are usually C-level descriptors.

Example:

xs.append(1)

The method append is implemented in C for list objects.

A highly optimized path can avoid creating a Python bound method object and call the list append implementation directly.

The source-level call:

xs.append(1)

therefore involves:

attribute or method resolution
argument setup
C method call
mutation of list object
return None

It is much faster than an equivalent method implemented in Python because the loop and mutation logic run in C.

31.36 Calls and super

super() is a callable and descriptor-aware object that changes method resolution.

class Base:
    def f(self):
        return 1

class Child(Base):
    def f(self):
        return super().f() + 1

The call:

super().f()

involves:

create or resolve super object
perform attribute lookup using adjusted MRO position
bind method to original instance
call method

This is ordinary call machinery plus special attribute resolution.

31.37 Calls and Properties

A property access may call code before the explicit call even begins.

obj.factory()

If factory is a property:

class C:
    @property
    def factory(self):
        return lambda: 42

Then:

obj.factory
    calls property getter
    returns callable

(...)
    calls returned callable

So the source expression contains an implicit call followed by an explicit call.

This is why CPython must treat attribute access as potentially effectful.

31.38 Calls and Error Cleanup

Suppose this call partially evaluates arguments, then fails:

f(g(), h())

If h() raises, then:

f has been loaded
g() has returned
h() raises
the call to f never happens
temporaries must be cleaned up
exception propagates

The stack may contain temporary references to f and g()’s result. CPython must release them correctly during error unwinding.

This is one of the reasons call bytecode and evaluation-loop error paths are careful.

31.39 Inspecting Calls

Use dis to inspect call bytecode.

import dis

def target(a, b):
    return a + b

def caller(x):
    return target(x, 10)

dis.dis(caller)

Inspect function metadata:

print(target.__code__.co_argcount)
print(target.__code__.co_posonlyargcount)
print(target.__code__.co_kwonlyargcount)
print(target.__defaults__)
print(target.__kwdefaults__)
print(target.__code__.co_varnames)

Inspect signatures at Python level:

import inspect

print(inspect.signature(target))

The inspect view is source-level and user-friendly. The code object view is closer to what CPython uses for frame setup.

31.40 A Minimal Call Interpreter

A toy interpreter can show the call model.

LOAD_CONST = "LOAD_CONST"
LOAD_FAST = "LOAD_FAST"
CALL = "CALL"
RETURN = "RETURN"

def run(code, consts, locals_):
    stack = []

    for op, arg in code:
        if op == LOAD_CONST:
            stack.append(consts[arg])

        elif op == LOAD_FAST:
            stack.append(locals_[arg])

        elif op == CALL:
            argc = arg
            args = stack[-argc:]
            del stack[-argc:]

            func = stack.pop()
            result = func(*args)
            stack.append(result)

        elif op == RETURN:
            return stack.pop()

    raise RuntimeError("missing RETURN")

Program:

def add(a, b):
    return a + b

code = [
    (LOAD_CONST, 0),   # add
    (LOAD_FAST, "x"),
    (LOAD_CONST, 1),   # 10
    (CALL, 2),
    (RETURN, None),
]

print(run(code, [add, 10], {"x": 5}))

Output:

15

This omits Python’s real argument binding, descriptors, C API, exceptions, frames, vectorcall, and reference counting. It still captures the stack idea: a call consumes callable and arguments, then pushes a result.

31.41 Common Misunderstandings

MisunderstandingCorrect model
A call always creates a Python frameBuilt-ins and C functions do not create Python frames for their bodies
Calling an async function runs it immediatelyIt creates a coroutine object
Calling a generator function runs until first yieldIt creates a generator object
Methods always allocate bound method objectsCPython often avoids this for immediate calls
Keyword arguments are always passed as a dictFast paths can avoid building a dict
def only declares a functiondef executes and creates a function object
Classes are not functions, so they are not callableClasses are callable through metaclass logic
obj() must be a function callIt may call type(obj).__call__

31.42 Reading Strategy

To study calls, start with simple source forms:

f()
f(a, b)
f(a=1)
f(*args)
f(**kwargs)
obj.method(x)
C(x)
async_fn()
gen_fn()

For each one:

import dis
dis.dis(function_containing_call)

Then ask:

How is the callable loaded?
How are arguments loaded?
Are keywords present?
Is unpacking present?
Does attribute lookup happen before the call?
Does binding happen?
Does the call create a Python frame?
Can the call return a coroutine or generator instead of running the body?
What cleanup is needed if argument evaluation fails?

This approach turns call syntax into concrete interpreter operations.

31.43 Chapter Summary

A function call in CPython is a structured runtime operation. The interpreter evaluates the callable and arguments, arranges them on the frame stack, selects the proper call protocol, binds arguments when needed, executes Python frames or native C implementations, then pushes a returned object or propagates an exception.

The central model is:

load callable
load arguments
CALL
    bind arguments
    run Python frame or native callable
    return object or raise exception
store or use result

Calls are expensive because they cross many runtime boundaries: stack layout, argument binding, descriptor binding, frame setup, C API conventions, reference ownership, exception handling, and specialization.

Much of CPython performance work focuses on making calls cheaper without changing Python’s dynamic semantics.