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 resultThe 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 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:
def g(a, b):
return f(a, b)Conceptually:
LOAD_GLOBAL f
LOAD_FAST a
LOAD_FAST b
CALL 2
RETURN_VALUEThe 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 exception31.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 resultsIf 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 + bhas two positional parameters.
Calling:
add(2, 3)binds:
a = 2
b = 3In CPython, the callee frame uses fast local slots.
Conceptually:
callee frame localsplus:
slot 0: a = 2
slot 1: b = 3Then the bytecode executes:
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
RETURN_VALUEThe 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 = 10CPython 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 **kwargsExample 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 + stepThe 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 = 1The famous mutable default behavior follows from this:
def append_item(x, xs=[]):
xs.append(x)
return xsThe 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 present31.9 Variadic Positional Arguments
A *args parameter collects extra positional arguments.
def f(a, *args):
return argsCall:
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 kwargsCall:
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 cleanup31.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 cCall:
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 resultThe 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 resultThe 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 failureThe 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 functionThis 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 10If 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 objectThen 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 = 10This 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 resultExample:
class C:
def __new__(cls, value):
obj = super().__new__(cls)
return obj
def __init__(self, value):
self.value = valueThe 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():
passConceptually:
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():
passmean:
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 xWhen called, its frame can access x through the closure cell.
Conceptually:
inner frame:
local y = 5
free variable x -> cell -> 10The 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 returnThis 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 coroConceptually:
call async function:
create coroutine object
return coroutine
await coroutine:
execute or resume coroutine frameThe 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 = 1Each 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 exceptionIf g has a handler:
def g():
try:
return f()
except ValueError:
return 0the 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():
passConceptually:
LOAD_CONST None
RETURN_VALUEA call expression always expects either:
a returned Python object
or an exceptionThere 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 anyThe 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.xcan call obj.__getattribute__.
Example:
a + bcan 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 B31.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 objectCPython 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 argumentsThis 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 resultIf 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 totalThe 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 exception31.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 callsThis 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 NoneIt 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() + 1The call:
super().f()involves:
create or resolve super object
perform attribute lookup using adjusted MRO position
bind method to original instance
call methodThis 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: 42Then:
obj.factory
calls property getter
returns callable
(...)
calls returned callableSo 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 propagatesThe 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:
15This 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:
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 resultCalls 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.