Dunder Methods: Making Objects Feel Built-in
About 20 minutes
Why this matters
Why does "a" + "b" work, and len([1, 2]), and 3 < 5? Because those types define special methods with double underscores (“dunder” methods). You can define the same methods on your own classes, so they work with +, ==, <, print, sorted and more.
The idea
class Money:
def __init__(self, cents):
self.cents = cents
def __repr__(self): # how it looks in the shell and in lists
return f"Money({self.cents})"
def __str__(self): # how print() and f-strings show it
return f"${self.cents / 100:.2f}"
def __eq__(self, other): # ==
if not isinstance(other, Money):
return NotImplemented # "I don't know how": Python falls back
return self.cents == other.cents
def __lt__(self, other): # < (also lets sorted() work)
return self.cents < other.cents
def __add__(self, other): # +
return Money(self.cents + other.cents)
Other favourites: __len__ (for len()), __contains__ (for in), __getitem__ (for obj[i]), __bool__. If you define only __repr__, str() uses it too. Define __repr__ for every class; it makes debugging far easier.
Try it
Add __len__ to a class that holds a list, then call len(obj). Print an object before and after you write __repr__.
Your turn
Write two classes:
Vector(x, y)with__repr__(Vector(1, 2)),__eq__(equal when both parts match; comparing with something that is not aVectormust giveFalse, not crash),__add__(add two vectors) and__mul__(multiply by a number:Vector(1, 2) * 3isVector(3, 6)).Version(text)for version numbers such as"1.10.2": store the numbers as a tuple of integers.__str__returns the original-looking text,__eq__compares versions, and__lt__orders them so thatVersion("1.10.0")is greater thanVersion("1.2.0")andsorted()works on a list of versions.
Recap
- Dunder methods let your class work with operators and built-in functions.
__repr__is for programmers,__str__is for users.- Return
NotImplementedfrom__eq__and__add__when the other type is not supported.
Common mistakes
- Comparing version strings as text:
"1.10" < "1.2"isTruebecause"1"is smaller than"2"as characters. - Returning a string from
__eq__or printing instead of returning inside__add__. - Defining
__eq__without thinking about__hash__: objects that define__eq__can no longer be dictionary keys or set members unless you add__hash__.