# 31. Function Calls

# 31. Function Calls

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:

```python id="wauh11"
result = f(1, 2)
```

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

```text id="rhdr0d"
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:

```python id="a7ltua"
callable_object(arguments)
```

The object before the parentheses must be callable.

Examples:

```python id="pqkmhl"
f()
len(xs)
obj.method(1)
C(10)
decorator(fn)
callback(event)
```

Many kinds of objects can be callable:

| Callable kind | Example | Runtime behavior |
|---|---|---|
| Python function | `f()` | Creates or initializes a Python frame |
| Built-in function | `len(xs)` | Calls C implementation |
| Bound method | `obj.method()` | Calls function with bound `self` |
| Class | `C()` | Calls metaclass construction path |
| Callable instance | `obj()` | Calls `obj.__call__` |
| C extension function | `mod.func()` | Calls native extension code |
| Coroutine function | `async_fn()` | Creates coroutine object |
| Generator function | `gen()` | Creates generator object |
| Descriptor result | `obj.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:

```python id="xszy0u"
def g(a, b):
    return f(a, b)
```

Conceptually:

```text id="gwqjm5"
LOAD_GLOBAL f
LOAD_FAST a
LOAD_FAST b
CALL 2
RETURN_VALUE
```

The stack before the call contains the callable and arguments:

```text id="rc6gwy"
[f, a, b]
```

The call instruction consumes them and pushes the return value:

```text id="q4x2va"
[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:

```text id="oc3h6f"
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:

```text id="2dz11l"
callable
arg0
arg1
arg2
...
```

For:

```python id="n90xfk"
f(x, y, z)
```

the stack before the call is conceptually:

```text id="0zk4e7"
[f, x, y, z]
```

After the call:

```text id="1ef6n3"
[result]
```

Keyword calls need additional metadata.

```python id="zewq6w"
f(x, y=10)
```

Conceptually, CPython must represent:

```text id="c35rsw"
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:

```python id="r11rqm"
f(a(), b(), c())
```

the calls happen left to right:

```text id="rg0lon"
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:

```python id="9b37sl"
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:

```python id="55c6o4"
def add(a, b):
    return a + b
```

has two positional parameters.

Calling:

```python id="wkxmb9"
add(2, 3)
```

binds:

```text id="jlsbey"
a = 2
b = 3
```

In CPython, the callee frame uses fast local slots.

Conceptually:

```text id="6u63lj"
callee frame localsplus:
    slot 0: a = 2
    slot 1: b = 3
```

Then the bytecode executes:

```text id="chrvpi"
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.

```python id="n28umo"
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:

```text id="j16i8j"
width  = 20
height = 10
```

CPython must check:

```text id="x7xbwk"
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:

```python id="rbyfsw"
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.

```python id="d2kqmt"
def f(x, step=1):
    return x + step
```

The function object stores the default value for `step`.

Conceptually:

```text id="zw5t1r"
function f
    code object
    globals
    defaults: (1,)
```

When called as:

```python id="24ks4e"
f(10)
```

CPython fills:

```text id="n9k9rw"
x = 10
step = 1
```

The famous mutable default behavior follows from this:

```python id="kjt1ip"
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.

```python id="53ae7c"
def connect(host, *, timeout, retries=3):
    ...
```

Valid:

```python id="kjp0p3"
connect("example.com", timeout=10)
```

Invalid:

```python id="g6g3ha"
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:

```text id="w1u0wk"
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.

```python id="hggzqm"
def f(a, *args):
    return args
```

Call:

```python id="ve43p4"
f(1, 2, 3)
```

Binding:

```text id="72r8al"
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.

```python id="842uzh"
def f(a, **kwargs):
    return kwargs
```

Call:

```python id="e27hzk"
f(1, x=2, y=3)
```

Binding:

```text id="z7vjci"
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:

```python id="2t6bo9"
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:

```python id="dlam5o"
f(0, *xs, *ys)
```

The runtime must preserve left-to-right ordering.

Conceptually:

```text id="g86xe4"
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:

```python id="gst2dz"
kwargs = {"x": 1, "y": 2}
f(**kwargs)
```

Multiple mappings can be supplied:

```python id="1obqtk"
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:

```python id="xha3n8"
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:

```python id="fou3a6"
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:

```text id="1fh4l4"
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.

```python id="u6ew7w"
def f(a, b):
    c = a + b
    return c
```

Call:

```python id="ry7s81"
f(2, 3)
```

Conceptually:

```text id="kdm5oh"
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.

```python id="47h9j2"
len(xs)
```

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

Conceptually:

```text id="v2dcxi"
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:

```c id="fugpzb"
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:

```text id="1edgq5"
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:

```text id="y0ul2m"
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:

```python id="i9fxmj"
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.

```python id="yvl8kj"
obj.method(10)
```

At the language level, this means:

```text id="ztwg7z"
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:

```python id="t8uh02"
bound = obj.method
bound(10)
```

The bound method carries:

```text id="qceh4k"
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:

```python id="bkvw5y"
class C:
    def f(self, x):
        return x

obj = C()
obj.f(10)
```

Conceptually:

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

This is why:

```python id="pmz8uo"
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.

```python id="0bc8io"
obj = C(1, 2)
```

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

Conceptual flow:

```text id="wy9ffx"
C.__call__(*args, **kwargs)
    calls C.__new__(C, *args, **kwargs)
    if result is instance of C:
        calls result.__init__(*args, **kwargs)
    returns result
```

Example:

```python id="j7g0io"
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__`.

```python id="jv1zpx"
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:

```python id="f9obht"
add10(5)
```

conceptually invokes:

```python id="o2fo9r"
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.

```python id="hxiutv"
@decorator
def f():
    pass
```

Conceptually:

```python id="57azij"
def f():
    pass

f = decorator(f)
```

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

Multiple decorators:

```python id="ew1w3e"
@d1
@d2
def f():
    pass
```

mean:

```python id="hq0xkt"
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.

```python id="ld9q93"
def outer(x):
    def inner(y):
        return x + y
    return inner

f = outer(10)
f(5)
```

The function `inner` has:

```text id="bmsuz9"
code object
globals
closure tuple containing cell for x
```

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

Conceptually:

```text id="lojpq8"
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.

```python id="1wn1lz"
def gen():
    yield 1

g = gen()
```

The call creates a generator object.

The function body starts when the generator is resumed:

```python id="xvobsw"
next(g)
```

Conceptually:

```text id="1gh51l"
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.

```python id="z4c0rv"
async def fetch():
    return 1

coro = fetch()
```

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

```python id="tmq7yx"
result = await coro
```

Conceptually:

```text id="3o4iuh"
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.

```python id="mmzccu"
def fact(n):
    if n <= 1:
        return 1
    return n * fact(n - 1)
```

For `fact(4)`:

```text id="v16tkx"
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.

```python id="zv234y"
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:

```text id="p6uolo"
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:

```python id="g7gg6a"
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`.

```python id="46x94k"
def f():
    pass
```

Conceptually:

```text id="99ydts"
LOAD_CONST None
RETURN_VALUE
```

A call expression always expects either:

```text id="q8p8m0"
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:

```text id="hinb3j"
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:

```c id="s4hvbw"
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:

```python id="yv3kfl"
obj.x
```

can call `obj.__getattribute__`.

Example:

```python id="44ak0w"
a + b
```

can call `a.__add__`.

Example:

```python id="72yoig"
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:

```text id="5p626a"
frame A
    executes LOAD_ATTR
        calls Python descriptor
            frame B
```

## 31.31 Method Call Optimization

This source pattern is common:

```python id="8wm8xu"
obj.method(arg)
```

A naive implementation would:

```text id="ywi46w"
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:

```text id="d28yc3"
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:

```python id="on96mz"
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:

```text id="ec11pe"
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:

```python id="sky0ma"
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:

```text id="nx4fmo"
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:

```text id="99b4lz"
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:

```python id="lz9y2o"
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:

```python id="bx4yzs"
xs.append(1)
```

therefore involves:

```text id="2d6rn4"
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.

```python id="xskypw"
class Base:
    def f(self):
        return 1

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

The call:

```python id="onpk7n"
super().f()
```

involves:

```text id="s1yqxl"
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.

```python id="utkq7j"
obj.factory()
```

If `factory` is a property:

```python id="q5y4ib"
class C:
    @property
    def factory(self):
        return lambda: 42
```

Then:

```text id="7onci0"
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:

```python id="xwgmze"
f(g(), h())
```

If `h()` raises, then:

```text id="9lvmys"
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.

```python id="rgh73f"
import dis

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

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

dis.dis(caller)
```

Inspect function metadata:

```python id="i0pt0v"
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:

```python id="dcrnsg"
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.

```python id="47u5eu"
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:

```python id="lq4boa"
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:

```text id="5boj0q"
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

| Misunderstanding | Correct model |
|---|---|
| A call always creates a Python frame | Built-ins and C functions do not create Python frames for their bodies |
| Calling an async function runs it immediately | It creates a coroutine object |
| Calling a generator function runs until first yield | It creates a generator object |
| Methods always allocate bound method objects | CPython often avoids this for immediate calls |
| Keyword arguments are always passed as a dict | Fast paths can avoid building a dict |
| `def` only declares a function | `def` executes and creates a function object |
| Classes are not functions, so they are not callable | Classes are callable through metaclass logic |
| `obj()` must be a function call | It may call `type(obj).__call__` |

## 31.42 Reading Strategy

To study calls, start with simple source forms:

```python id="s1he1e"
f()
f(a, b)
f(a=1)
f(*args)
f(**kwargs)
obj.method(x)
C(x)
async_fn()
gen_fn()
```

For each one:

```python id="cvm0yb"
import dis
dis.dis(function_containing_call)
```

Then ask:

```text id="8l87gs"
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:

```text id="oiwfbx"
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.
