Introduction to Dunder Methods0%

Introduction to Dunder Methods

Beginner12 min readUpdated: Jul 11, 2026
Study Materials

Introduction to Dunder Methods

Special methods in Python—widely referred to as dunder methods (short for "double underscore" methods) or magic methods—are the foundational building blocks of the Python Data Model. They allow user-defined classes to hook directly into the Python language runtime, enabling instances to exhibit native behaviors such as iteration, context management, indexing, slicing, callable invocation, arithmetic operations, and string representation.

When you execute expressions such as len(my_obj), x + y, with my_obj:, or print(my_obj), Python does not invoke ad-hoc runtime checks; instead, it delegates these high-level language constructs to well-defined internal protocol methods: my_obj.__len__(), type(x).__add__(x, y), my_obj.__enter__(), and type(my_obj).__str__(my_obj).

Understanding how dunder methods work at both the language and interpreter levels allows you to write idiomatic, elegant, and highly expressive Python frameworks.


1. The Python Data Model & Special Method Lookup

The Python Data Model defines a formal interface that objects can implement to interact with built-in protocols. In Python's C API (CPython), classes have predefined C-level struct slots (such as tp_new, tp_init, tp_repr, tp_call, tp_as_number, and tp_as_sequence). When you define a dunder method in Python, CPython populates these corresponding slots for blazing-fast runtime dispatch.

Output
User Code / Built-in Function
len(collection)
CPython Evaluates Language Slot
(Look up type(collection).__len__)
┌────────────┴────────────┐
▼ ▼
Slot Found & Populated Slot Is NULL / Missing
Invoke __len__(collection) Raise TypeError: object of type
'X' has no len()

The Class Lookup Rule (Critical Nuance)

A foundational architectural design decision in Python is that special methods are almost always looked up on the class (the type), not on the instance itself.

If you dynamically assign a dunder method to an instance dictionary (self.__len__ = lambda: 5), built-in operations such as len(self) will not trigger it. The interpreter bypasses instance.__dict__ for special methods to guarantee performance and avoid infinite recursion within the metaclass layer.

Python
class BrokenHook:
pass
 
obj = BrokenHook()
 
# Attaching to instance __dict__
obj.__len__ = lambda: 42
 
try:
print(len(obj))
except TypeError as err:
print("Caught TypeError:", err)
# Output: object of type 'BrokenHook' has no len()
 
# Correct approach: Bind to the class
BrokenHook.__len__ = lambda self: 42
print("Class-level hook:", len(obj)) # Output: 42

2. Object Lifecycle: __new__ vs __init__ vs __del__

Every Python object undergoes three distinct lifecycle phases: Allocation/Creation, Initialization, and Destruction.

Lifecycle HookRoleReturn ValueSignature
__new__(cls, ...)Allocator / Constructor. Creates the raw instance in memory.Must return an instance of cls (or another class).__new__(cls, *args, **kwargs)
__init__(self, ...)Initializer. Configures attributes on the pre-allocated instance.Must return None.__init__(self, *args, **kwargs)
__del__(self)Finalizer. Executed when the reference count drops to zero before garbage collection.Must return None.__del__(self)
Output
Class Instantiation: MyClass(*args)
Step 1: Call MyClass.__new__(cls, *args)
(Allocates memory for raw object)
Is returned object an instance of MyClass?
┌────────────────┴────────────────┐
YES NO
│ │
▼ ▼
Step 2: Call MyClass.__init__(instance, *args) Skip __init__()
(Assigns instance state & attributes) (Return object directly)
│ │
└────────────────┬────────────────┘
Ready Object Returned

Customizing Instance Allocation with __new__

Because __new__ runs before self exists, it is a static method (though implicitly marked, taking cls as its first argument). It is commonly used for:

  1. 1
    Subclassing immutable types like int, str, or tuple.
  2. 2
    Implementing the Singleton pattern.
  3. 3
    Metaprogramming and factory dispatch.

Visual Architecture & Process Flow

How data and code flow step-by-step

Flowchart
Step 1
def __init__
self, connection_string: str
Step 2
None:

3. Creating Immutable Data Types Subclassing Built-ins

When subclassing immutable objects like str or tuple, modifying self inside __init__ is impossible because the value has already been frozen during memory allocation. Modifying values requires intercepting __new__:

Python
class SanitizedSlug(str):
"""An immutable string subclass that guarantees lowercased, hyphenated text."""
 
def __new__(cls, value: str):
# Clean and sanitize before memory allocation
cleaned_value = value.strip().lower().replace(" ", "-")
# Delegate to str's allocator
return super().__new__(cls, cleaned_value)
 
slug = SanitizedSlug(" Advanced Python Dunder Methods 101 ")
print(f"Sanitized slug: '{slug}'")
print(f"Type check: {type(slug).__name__}, Is instance of str: {isinstance(slug, str)}")

4. Object Destruction with __del__ (Finalizers)

__del__ is called when an object's reference count drops to zero or during garbage collection cycle reclamation.

Important Warning
Do not rely on __del__ for critical resource cleanup (such as flushing database transactions or closing network sockets). Circular references, unexpected interpreter shutdowns, or dangling exceptions can delay or entirely suppress __del__ execution. Always prefer context managers (with statements).
Python
import sys
 
class EphemeralResource:
def __init__(self, resource_id: str) -> None:
self.resource_id = resource_id
print(f"Resource {self.resource_id} created.")
 
def __del__(self) -> None:
print(f"Resource {self.resource_id} is being collected.")
 
# Reference tracking demonstration
res = EphemeralResource("R-9021")
alias = res
 
print(f"Reference count before del: {sys.getrefcount(res) - 1}") # Minus temporary ref from getrefcount
del res # Reference count decreases to 1; __del__ not yet called
print("Deleted 'res' variable; alias remains in scope.")
 
del alias # Reference count drops to 0; __del__ triggers immediately
print("Script execution continues...")

5. Overview of Core Special Method Categories

Python organizes dunder methods into distinct functional protocols:

CategoryRepresentative MethodsPurpose
Object Representation__repr__, __str__, __format__, __bytes__String rendering, debugging, and byte formatting
Attribute Access__getattr__, __getattribute__, __setattr__, __delattr__Dynamic attribute interception and fallback handling
Sequence & Mapping__len__, __getitem__, __setitem__, __delitem__, __contains__Emulating lists, dictionaries, indexing, and membership tests
Iteration__iter__, __next__, __reversed__Iteration protocols and generator loops
Callables__call__Permitting instances to be executed like functions
Context Managers__enter__, __exit__Safe acquisition and release of runtime resources
Arithmetic Operators__add__, __sub__, __mul__, __truediv__, __matmul__Overloading mathematical operators
Comparisons__eq__, __ne__, __lt__, __le__, __gt__, __ge__Rich comparisons, sorting, and equality verification

6. Summary and Best Practices

  1. 1
    Protocol Over Class Inheritance: Python adheres to duck typing. If an object implements __iter__ and __next__, it is an iterator; if it implements __getitem__ and __len__, it is a sequence.
  2. 2
    Never Invent Custom Dunder Names: Do not define custom methods like __my_custom_func__. The double underscore namespace is explicitly reserved by the Python core developers for future language enhancements.
  3. 3
    Keep __new__ and __init__ Signatures Aligned: If __new__ takes arguments, __init__ should accept those exact arguments, as Python automatically routes instantiation parameters to both.
  4. 4
    Prefer Context Managers Over __del__: Explicit resource termination via __enter__ and __exit__ avoids non-deterministic garbage collector behavior.

Multiple Choice Questions

1.

Which dunder method is the actual allocator responsible for creating and returning a new instance in memory before initialization? A. __init__ B. __new__ C. __call__ D. __prepare__

Answer: B
Explanation:__new__ is the static constructor/allocator method that creates and returns a new object instance. __init__ only receives this already-allocated instance (self) to populate its attributes.

2.

What happens if you dynamically bind a dunder method to an instance dictionary, such as instance.__len__ = lambda: 10, and then execute len(instance)? A. len(instance) successfully returns 10. B. Python raises a TypeError stating that the object has no len(), because special methods are looked up on the class. C. Python updates the class dictionary dynamically and prints 10. D. A SyntaxError is raised immediately upon assignment.

Answer: B
Explanation:Python's Data Model dictates that special methods are looked up on the object's class (its type), bypassing the instance __dict__ for speed and consistency. Therefore, len(instance) fails with a TypeError.

3.

When subclassing an immutable built-in data type like str or int to modify its value prior to creation, which method must be overridden? A. __init__ B. __del__ C. __new__ D. __str__

Answer: C
Explanation:Because immutable objects cannot have their values altered once created, any transformation of the initial value must take place in __new__ before the immutable memory block is allocated and frozen.

4.

What is the expected return value of the __init__ method? A. The newly created instance (self) B. 0 for success or -1 for failure C. None D. A boolean True

Answer: C
Explanation:__init__ must always return None. Returning any non-None value from __init__ raises a runtime TypeError: __init__() should return None.

5.

Why is relying on __del__ for releasing critical resources (like network connections or file descriptors) discouraged in production Python? A. __del__ is deprecated in Python 3. B. Garbage collection timing is non-deterministic, and cyclic references or abnormal interpreter exits can prevent __del__ from running promptly. C. __del__ cannot access instance attributes. D. Calling del obj always deletes the object instantly regardless of remaining references.

Answer: B
Explanation:Python's garbage collector does not guarantee prompt invocation of __del__, especially in the presence of circular references or sudden program termination. Context managers (with statements) should be used for deterministic cleanup.

Next Lesson

__str__, __repr__, __len__

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Related Lessons

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext