Python: Special (Dunder) Methods

Dunder methods are how Python connects your objects to the language itself. Master them and your classes will work seamlessly with built-in syntax, operators, and standard library tools.

What Are Dunder Methods?

Dunder is short for double underscore. Every method whose name is wrapped in double underscores, such as __init__ or __len__, is a dunder method. They are also called magic methods or special methods, though the Python documentation prefers "special methods."

You do not call these methods directly most of the time. Instead, Python calls them automatically in response to specific syntax or built-in function calls. When you writelen(my_obj), Python calls my_obj.__len__() behind the scenes. When you write a + b, Python calls a.__add__(b). This is what gives Python its clean, expressive syntax: everything is consistent because the same protocol applies to built-in types and to your own classes alike.

Why Dunder Methods Matter

Rather than forcing new objects into awkward method calls, dunder methods let your classes feel native to the language.

  • They hook into Python built-in functions: len(), str(), repr(), bool(), hash(), iter().
  • They make operators like +, -, *, /, ==, <, > work on custom objects.
  • They enable the "with" statement, the "in" operator, subscript notation (obj[key]), and callable syntax (obj()).
  • You only implement the ones you need. There is no requirement to implement them all.

__init__ and __del__

__init__ is the initializer. Python calls it immediately after creating a new instance, passing any arguments you provided to the class call. Its job is to set up the object's initial state by assigning instance variables. Note that __init__does not create the object; that is handled by __new__, which you rarely need to override.

__del__ is the finalizer, called when an object is about to be garbage collected. It is the right place to release resources that Python's garbage collector cannot handle automatically, such as database connections or file locks. That said, keep __del__ as a last resort. Context managers (covered later) are the cleaner, more predictable way to manage resources.

Initializer and Finalizer

Python

Controlling what happens when an object is born and when it is cleaned up.

__str__ and __repr__

Every object in Python has two string representations. __str__ is for end users: it should be readable and descriptive. __repr__ is for developers: it should be unambiguous, and ideally should be a valid Python expression that could recreate the object. When you call print(obj), Python uses __str__. When you inspect an object in the REPL or inside a list or dictionary, Python uses __repr__.

If you only define one, define __repr__. Python falls back to it for both purposes if __str__ is not defined.

__str__ and __repr__ in Practice

Python

One representation for users, another for debugging.

__len__ and __getitem__

__len__ is called by the built-in len() function. It must return a non-negative integer. Implementing it also makes your object behave truthily in boolean contexts: an object with a length of zero is considered falsy (unless you also define__bool__, which takes precedence).

__getitem__ enables subscript access: obj[key]. When you implement both__len__ and __getitem__, Python gives you iteration support for free, without needing __iter__. Python will call __getitem__ with increasing integer indices starting from 0 until an IndexError is raised.

Building a Custom Sequence

Python

__len__ and __getitem__ make your class behave like a list.

__setitem__ and __delitem__

These two complete the subscript interface. __setitem__ is triggered by assignment: obj[key] = value. __delitem__ is triggered by thedel statement: del obj[key]. Together with __getitem__, they let you build objects that feel exactly like dictionaries or lists from the outside, while storing or validating data however you choose internally.

A Typed Dictionary

Python

Enforcing value types through __setitem__.

__contains__ and the "in" Operator

The in membership operator calls __contains__. It should returnTrue or False. If you do not define __contains__ but you do define __iter__, Python falls back to iterating through all elements looking for a match. If you define neither, Python falls back to __getitem__. Defining __contains__ explicitly is best when you can provide a more efficient lookup than a full scan.

Custom Membership Testing

Python

Providing an efficient __contains__ implementation.

__iter__ and __next__

To make an object iterable (usable in a for loop, list(), tuple(), unpacking, etc.), you implement the iterator protocol. This requires two methods. __iter__ must return the iterator object itself. __next__must return the next value in the sequence, or raise StopIteration when there are no more values left.

A common pattern is to make the class both iterable and its own iterator by returning self from __iter__. Note that once an iterator is exhausted, it stays exhausted. If you need to restart from the beginning, you need a fresh instance.

A Custom Range Iterator

Python

Implementing the full iterator protocol from scratch.

__call__: Callable Objects

Defining __call__ lets you invoke an instance as if it were a function. This is useful when you need an object that behaves like a function but also needs to carry persistent state between calls. A common real-world use is building configurable function-like objects, counters, memoization wrappers, or machine learning models (PyTorch's nn.Module relies heavily on this).

You can check whether any object is callable with the built-incallable(obj) function, which returns True if the object has a__call__ method.

A Stateful Multiplier

Python

An instance that remembers its configuration across calls.

__enter__ and __exit__: Context Managers

The with statement is Python's standard way to handle setup and teardown around a block of code. To support it, your class needs two methods. __enter__ is called at the start of the with block; whatever it returns is bound to theas variable. __exit__ is called at the end, even if an exception occurs inside the block.

__exit__ receives three arguments: the exception type, the exception value, and the traceback. If no exception occurred, all three are None. If__exit__ returns a truthy value, the exception is suppressed. If it returnsNone or False, the exception propagates normally.

A Timer Context Manager

Python

Measuring elapsed time around any block of code.

__exit__ Signature Explained

The three parameters let you inspect and optionally handle any exception that occurred inside the 'with' block.

  • exc_type: the class of the exception (e.g., ValueError), or None if no exception.
  • exc_val: the exception instance itself, or None.
  • exc_tb: the traceback object, useful for logging, or None.
  • Return True to silently suppress the exception; return False or None to let it propagate.

Comparison Dunder Methods

Python has six comparison operators, each backed by a dunder method: __eq__(==), __ne__ (!=), __lt__ (<), __le__ (<=),__gt__ (>), and __ge__ (>=). By default, __eq__ compares by identity (same as is). Overriding it lets you define what "equal" means for your class.

A useful shortcut: if you decorate your class with@functools.total_ordering, you only need to implement __eq__ and one of the ordering methods. Python derives the other four automatically.

Sorting Custom Objects

Python

Implementing comparison methods to make objects sortable.

Arithmetic Dunder Methods

The arithmetic operators are each tied to a dunder method: __add__ (+),__sub__ (-), __mul__ (*), and __truediv__ (/). There are also reflected variants like __radd__, which Python calls on the right operand when the left operand does not know how to handle the operation. And in-place variants like __iadd__ for +=.

A well-designed arithmetic dunder should return a new object of the same type, leaving self unchanged. This mirrors how Python's built-in numbers behave.

A 2D Vector Class

Python

Arithmetic operators on a custom numeric type.

__hash__

__hash__ returns an integer that Python uses to place your object in a hash table. This is what allows objects to be used as dictionary keys or stored in sets. The hash value must be stable for the lifetime of the object, and two objects that compare equal with == must have the same hash value.

This last rule is why Python automatically sets __hash__ to Nonewhenever you define __eq__ without also defining __hash__. Objects in that state are no longer hashable. If you want them to be usable as dictionary keys, you need to explicitly define __hash__ to be consistent with__eq__.

Making Objects Hashable

Python

Defining __hash__ consistently with __eq__.

__bool__

__bool__ determines the truthiness of an object in boolean contexts: if obj,while obj, not obj, and so on. It must return True or False. If __bool__ is not defined, Python falls back to __len__: an object with length zero is falsy. If neither is defined, the object is always truthy (since it exists and is not None).

Truthiness Based on State

Python

A shopping cart that is falsy when empty.

__sizeof__

__sizeof__ returns the size of the object in bytes, not counting the memory consumed by objects it references. It is called by sys.getsizeof(), which adds the garbage collector overhead on top of what __sizeof__ returns. This is mostly used for profiling memory usage in performance-sensitive code.

You rarely need to override this. The default implementation from objectworks correctly for most classes. If you implement __slots__ or otherwise change how attributes are stored, overriding __sizeof__ gives you accurate size reporting.

Inspecting Memory Usage

Python

Using sys.getsizeof with custom and built-in types.

Putting It All Together: A Full Example

The real power of dunder methods shows when you combine several of them. The class below implements a simple ordered bag (a collection that tracks how many times each item appears). It supports length, subscripting, iteration, membership testing, addition, truthiness, and clean string representations.

An Ordered Bag Collection

Python

Multiple dunder methods working together.

Quiz - Test Your Knowledge

Ten questions covering the full scope of Python's special methods. Some questions test the mechanics of specific dunders; others test the rules and interactions between them. Think carefully before selecting an answer.

Knowledge Check

1. Which dunder method does Python call when you write len(obj)?

2. What is the key difference between __str__ and __repr__?

3. Which pair of methods do you implement to make your object work with a for loop?

4. What happens when __next__ has no more items to yield?

5. Which two methods must a class implement to work as a context manager with the "with" statement?

6. What does __call__ allow you to do?

7. If you define __eq__ on a class, what happens to the object's __hash__ by default?

8. Which dunder method controls the truthiness of an object when used in an if statement?

9. What does __contains__ enable?

10. Which dunder method is invoked when you write obj[key] = value?