Class Mechanics & Instantiation Protocol
Master CPython class construction: runtime type execution, the two-phase instantiation protocol (__new__ vs __init__), and instance dictionary binding.
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__.
__new__ allocates raw object memory; __init__ populates instance attributes.
1. Class Construction at Runtime#
When the Python interpreter evaluates a class block:
- It extracts the class name, tuple of base classes, and any keyword arguments (such as
metaclass=...). - It calls the metaclass
__prepare__method to create a namespace dictionary. - It executes the class body bytecode inside that namespace mapping.
- It calls
type.__new__to construct thePyTypeObjectstruct on the heap.
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:
1234567891011121314151617181920class 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!
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__:
- CPython inspects the class to ensure
nameis not a data descriptor. - The key
"name"(interned string) and value"Alice"are written directly intoobj.__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:
What is the precise structural difference between __new__ and __init__ in Python?
What happens if a custom __new__ method returns an object that is NOT an instance of the class being called?