Python call Method Execution Flow Explained

When you invoke a Python object as if it were a function, Python relies on the __call__ method to execute that request. This article details the internal execution flow of invoking an instance, explaining how the runtime translates call syntax into bytecode, why it bypasses the instance dictionary to look up the method on the class, how the CPython tp_call slot manages the execution, and how control returns to the caller.

1. Bytecode Generation

The execution begins when the Python interpreter compiles the call expression instance(*args, **kwargs). At the bytecode level, this syntax generates a call instruction—such as CALL in Python 3.11+ or CALL_FUNCTION in earlier versions. The interpreter evaluates the callable object and its arguments, pushing them onto the evaluation stack.

2. Bypassing the Instance Dictionary

Unlike standard attribute lookups, special methods (dunder methods) bypass the instance's __dict__ entirely. Python does not check instance.__dict__['__call__']. Instead, it retrieves the type of the instance using type(instance).

This design choice ensures performance optimization and consistency:

3. Resolving the tp_call Slot in CPython

In the CPython implementation, all Python types are represented by a PyTypeObject struct. This struct contains predefined function pointers known as "type slots."

4. Method Binding and Argument Passing

Once Python confirms that tp_call is present, it invokes slot_tp_call. This function handles the conversion between the internal C representation and Python's call mechanics:

  1. It retrieves the Python function object corresponding to __call__ from the class hierarchy.
  2. It prepends the target instance as the first argument (self), transforming the call into Class.__call__(instance, *args, **kwargs).
  3. It packages positional arguments as a tuple and keyword arguments as a dictionary.

5. Execution of the Method Body

With the arguments bound, Python creates a new frame on the call stack for the __call__ method. The interpreter switches context to execute the bytecode inside the method's body, executing arbitrary logic defined by the user.

6. Value Return and Stack Cleanup

When the method completes: