How Python Calls Functions Under the Hood
This article explains the low‑level mechanics of Python function calls, distinguishing Python‑implemented and C‑implemented functions, dissecting the bytecode generated for a simple call, and walking through the CPython CALL instruction, stack layout, method handling, and the relationship between PyFunctionObject, PyFrameObject, and PyCodeObject.
In the previous article we examined how Python functions are represented internally, showing that a Python‑implemented function is an instance of PyFunctionObject whose type object is <class 'function'>, while a C‑implemented function or method is an instance of PyCFunctionObject with type <class 'builtin_function_or_method'>.
Python‑implemented functions are created with the def keyword, whereas built‑in functions such as sum or "".join are C‑implemented. The following snippet demonstrates the type differences:
def foo():
pass
class A:
def foo(self):
pass
print(type(foo)) # <class 'function'>
print(type(A().foo)) # <class 'method'>
print(type(sum)) # <class 'builtin_function_or_method'>
print(type("".join)) # <class 'builtin_function_or_method'>To see how a function call is compiled, we use the dis module on a simple function foo(a, b) and its invocation foo(1, 2). The disassembly shows the generated bytecode, including the CALL instruction that performs the actual call:
import dis
code_string = """
def foo(a, b):
return a + b
foo(1, 2)
"""
dis.dis(compile(code_string, "<file>", "exec"))The resulting bytecode (excerpt) is:
0 RESUME 0
2 LOAD_CONST 0 (<code><code object foo at 0x7f...></code>)
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (foo)
8 PUSH_NULL
10 LOAD_NAME 0 (foo)
12 LOAD_CONST 1 (1)
14 LOAD_CONST 2 (2)
16 CALL 2
24 POP_TOP
26 RETURN_CONST 3 (None)The CALL opcode triggers the logic defined in CPython's TARGET(CALL) implementation. The interpreter first builds a stack layout where the bottom element is a NULL placeholder, followed by the callable object and its arguments. The NULL is introduced by the preceding PUSH_NULL instruction and serves two purposes: it reserves space for the return value and unifies the stack shape for both plain function calls and method calls.
TARGET(CALL) {
// runtime stack (bottom → top): NULL, function, arg1, arg2, ...
PyObject **args = (stack_pointer - oparg);
PyObject *callable = stack_pointer[-(1 + oparg)];
PyObject *method = stack_pointer[-(2 + oparg)];
int is_meth = method != NULL;
int total_args = oparg;
// ... handle method binding, adjust args, total_args ...
if (is_meth) {
callable = method;
args--;
total_args++;
}
// ... vectorcall handling for Python functions ...
res = PyObject_Vectorcall(callable, args,
total_args | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames);
// clean up stack and return
}If the callable is a Python function (type &PyFunction_Type) and the interpreter is in the classic evaluation mode, the call is inlined via _PyFunction_Vectorcall. A new interpreter frame is created with _PyEvalFramePushAndInit, locals are set based on the code object's co_flags, and the frame is dispatched. Otherwise, the generic PyObject_Vectorcall is used.
Finally, the article clarifies the relationship between PyFunctionObject, PyFrameObject, and PyCodeObject. While a PyFunctionObject packages a PyCodeObject together with its global namespace, the actual execution is driven by a PyFrameObject created from the same PyCodeObject. Thus the function object disappears once the frame starts executing, and the frame and code object remain tightly coupled.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Satori Komeiji's Programming Classroom
Python and Rust developer; I write about any topics you're interested in. Follow me! (#^.^#)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
