Python - Python Data Model (Deep Dive into Dunder Methods)
The Python Data Model defines how objects behave and interact within the Python interpreter. It is essentially a set of rules and protocols that govern how objects respond to built-in operations such as addition, comparison, attribute access, iteration, and more. These behaviors are implemented using special methods, commonly known as dunder methods (double underscore methods), such as __init__, __str__, __len__, etc.
Understanding and mastering the Python Data Model allows you to create objects that behave like built-in types and integrate seamlessly with Python syntax.
1. Object Creation and Initialization
Object creation in Python involves two important methods:
-
__new__(cls, ...): Responsible for creating a new instance of a class. It is a static method and is rarely overridden unless you need control over instance creation (for example, implementing immutable types or singletons). -
__init__(self, ...): Initializes the instance after it is created.
Example:
class Sample:
def __new__(cls):
print("Creating instance")
return super().__new__(cls)
def __init__(self):
print("Initializing instance")
Here, __new__ runs first, followed by __init__.
2. String Representation
These methods define how objects are represented as strings:
-
__str__(self): Returns a user-friendly string (used byprint()). -
__repr__(self): Returns an unambiguous string representation (used in debugging and interpreter).
Example:
class Person:
def __init__(self, name):
self.name = name
def __str__(self):
return f"Person: {self.name}"
def __repr__(self):
return f"Person('{self.name}')"
3. Operator Overloading
Dunder methods allow you to define how operators behave with objects:
-
__add__(self, other)for+ -
__sub__(self, other)for- -
__eq__(self, other)for== -
__lt__(self, other)for<
Example:
class Vector:
def __init__(self, x):
self.x = x
def __add__(self, other):
return Vector(self.x + other.x)
This allows objects of Vector to be added using +.
4. Attribute Access Control
These methods give fine-grained control over attribute access:
-
__getattr__(self, name): Called when an attribute is not found. -
__getattribute__(self, name): Called for every attribute access. -
__setattr__(self, name, value): Called when setting an attribute. -
__delattr__(self, name): Called when deleting an attribute.
Example:
class Demo:
def __getattr__(self, name):
return "Attribute not found"
Use __getattribute__ carefully because it intercepts all attribute access and can easily lead to recursion errors.
5. Container and Collection Behavior
To make objects behave like sequences or collections:
-
__len__(self)→len(obj) -
__getitem__(self, key)→obj[key] -
__setitem__(self, key, value)→obj[key] = value -
__iter__(self)→ iteration support
Example:
class MyList:
def __init__(self, data):
self.data = data
def __len__(self):
return len(self.data)
def __getitem__(self, index):
return self.data[index]
6. Callable Objects
Objects can be made callable like functions using:
-
__call__(self, ...)
Example:
class Multiply:
def __init__(self, factor):
self.factor = factor
def __call__(self, value):
return value * self.factor
Now, Multiply(2)(5) returns 10.
7. Context Management
To support the with statement:
-
__enter__(self) -
__exit__(self, exc_type, exc_value, traceback)
Example:
class FileHandler:
def __enter__(self):
print("Entering")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting")
8. Memory Optimization with __slots__
__slots__ restricts dynamic attribute creation and reduces memory usage by avoiding __dict__.
Example:
class Point:
__slots__ = ['x', 'y']
This is useful when creating many instances of a class.
9. Comparison and Hashing
-
__eq__(self, other)defines equality. -
__hash__(self)allows objects to be used in sets and as dictionary keys.
Important rule: if two objects are equal, their hash values must also be equal.
10. Customizing Truth Value
-
__bool__(self)defines truthiness of an object. -
If not defined, Python falls back to
__len__().
Example:
class Test:
def __bool__(self):
return False
Conclusion
The Python Data Model is what makes Python highly flexible and expressive. By implementing dunder methods, you can:
-
Make custom objects behave like built-in types
-
Integrate with Python syntax and operators
-
Control object lifecycle, memory, and interaction
-
Build clean, intuitive APIs
A deep understanding of these methods allows you to write more Pythonic, efficient, and maintainable code, especially in advanced applications such as frameworks, libraries, and system-level programming.