OOP Foundations & Object Lifecycle
Master object creation hooks, memory layout, descriptor protocols, dunder method mechanics, and attribute resolution edge cases.
Theoretical Mechanics & Memory Architecture
Python's object-oriented architecture is dynamic, introspective, and descriptor-driven. Classes and instances are heap objects backed by namespace dictionaries (__dict__). Attribute lookups, inheritance traversal, and method bindings are strictly orchestrated by the C3 Linearization algorithm (Method Resolution Order, or MRO) and the Descriptor Protocol (__get__, __set__, __delete__).
When evaluating instance.attr, CPython executes a deterministic lookup hierarchy: (1) Data descriptors on the class defining __set__ or __delete__, (2) The instance's own __dict__, (3) Non-data descriptors on the class (such as methods or property getters), (4) Standard class attributes, (5) Base classes according to MRO, and (6) Fallback to __getattr__.
Defining a mutable container at class scope (such as class Team: members = []) binds a single shared list to the class object in memory. Any instance appending to self.members mutates state across all instances. Mutable attributes must always be instantiated per instance inside __init__ via self.members = [].
Interactive Dry-Run Execution Workspace
__new__ Returning Existing Instance Bypasses __init__?
Trace CPython execution step-by-step and deduce the exact stdout string emitted by this snippet.
class Singleton:
_inst = None
def __new__(cls, val):
if cls._inst is None:
cls._inst = super().__new__(cls)
return cls._inst
def __init__(self, val):
self.val = val
a = Singleton(1)
b = Singleton(2)
print(a.val, b.val, a is b)Frequently Asked Questions on OOP Foundations & Object Lifecycle
Why do mutable class attributes cause unexpected cross-instance state pollution?
Class attributes are evaluated once when the class definition executes and reside in the class __dict__. When an instance accesses self.attr without rebinding it, Python resolves to the shared class attribute. In-place mutations modify the shared class instance.
How does the C3 Linearization algorithm determine Method Resolution Order in multiple inheritance?
C3 Linearization guarantees two invariants: child classes precede parent classes, and the order of base classes in the class declaration is strictly preserved. It computes a monotonic merge of parent MROs, raising TypeError if an inconsistent hierarchy is declared.
How does declaring __slots__ optimize memory consumption in Python classes?
__slots__ instructs CPython to allocate a fixed-size array of descriptor pointers directly on the instance struct, bypassing the creation of a dynamic __dict__ for each instance. This saves hundreds of bytes per object in large-scale deployments.