Class Mechanics & Instantiation Protocol

Master CPython class construction: runtime type execution, the two-phase instantiation protocol (__new__ vs __init__), and instance dictionary binding.

03. Object-Oriented Systems, Descriptor Protocol & Metaprogramming7 min read

In Python, classes are not passive syntax templates; they are runtime instances of type objects (type).

When a class statement evaluates, CPython executes the class body code inside a temporary namespace mapping, constructing a live PyTypeObject struct on the heap. When an instance is requested, CPython executes a rigorous two-phase protocol: memory allocation via __new__ followed by state initialization via __init__.

Instantiation PipelineObject Model
Two-Phase

__new__ allocates raw object memory; __init__ populates instance attributes.

1. Class Construction at Runtime#

When the Python interpreter evaluates a class block:

  1. It extracts the class name, tuple of base classes, and any keyword arguments (such as metaclass=...).
  2. It calls the metaclass __prepare__ method to create a namespace dictionary.
  3. It executes the class body bytecode inside that namespace mapping.
  4. It calls type.__new__ to construct the PyTypeObject struct on the heap.
Architecture Diagram
Rendering diagram...

2. The Instantiation Protocol: __new__ vs __init__#

Instantiating a class C(*args, **kwargs) invokes the type's tp_call slot, which executes a two-stage operational protocol:

Stage 1 (Allocation):obj=C.__new__(C,∗args,∗∗kwargs)\text{Stage 1 (Allocation)}: \quad \text{obj} = C.\text{\_\_new\_\_}(C, *args, **kwargs)
Stage 2 (Initialization):if isinstance(obj,C)  ⟹  C.__init__(obj,∗args,∗∗kwargs)\text{Stage 2 (Initialization)}: \quad \text{if } \text{isinstance}(\text{obj}, C) \implies C.\text{\_\_init\_\_}(\text{obj}, *args, **kwargs)
Architecture Diagram
Rendering diagram...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SingletonDatabase: _instance = None def __new__(cls, *args, **kwargs): # Stage 1: Static memory allocation / factory constructor if cls._instance is None: print("Allocating new memory block via object.__new__") cls._instance = super().__new__(cls) return cls._instance def __init__(self, connection_string: str): # Stage 2: Instance mutation print(f"Initializing connection to: {connection_string}") self.connection_string = connection_string # Verification db1 = SingletonDatabase("postgres://localhost:5432/db1") db2 = SingletonDatabase("postgres://localhost:5432/db2") print(f"db1 is db2: {db1 is db2}") # True: Identical memory address!
Subclassing Immutable Types
Because immutable objects (like int, str, and tuple) cannot have their internal memory mutated once allocated, customizing their values requires overriding __new__ rather than __init__. Modifying attributes in __init__ is too late!

3. Instance Dictionary Binding#

By default, every Python instance contains a pointer to an instance dictionary: obj.__dict__.

When you execute an assignment like self.name = "Alice" inside __init__:

  1. CPython inspects the class to ensure name is not a data descriptor.
  2. The key "name" (interned string) and value "Alice" are written directly into obj.__dict__.

Because dictionaries in Python are dynamically resizable hash tables, this enables Python's hallmark dynamic flexibility: any instance can have arbitrary attributes added or removed at runtime!

4. Concept Verification#

Test your understanding of class mechanics and instantiation:

Concept check

What is the precise structural difference between __new__ and __init__ in Python?

Concept check

What happens if a custom __new__ method returns an object that is NOT an instance of the class being called?