Special methods
Methods with double underscores that let objects work with the ==, <, + and in operators, and with the len() function.
Example
#class Score:
def __init__(self, student, points):
self.student = student
self.points = points
def __eq__(self, other):
if not isinstance(other, Score):
return NotImplemented
return self.points == other.points
def __lt__(self, other):
if not isinstance(other, Score):
return NotImplemented
return self.points < other.points
def __repr__(self):
return f"Score({self.student!r}, {self.points})"
a = Score("Emma", 92)
b = Score("Jack", 78)
c = Score("Mia", 92)
print(a == c, a != b)
print(b < a, a > b)
print(sorted([a, b, c]))
print(max([a, b, c]))Definition and usage
#Special methods, also called magic methods or "dunder" methods (from double underscore), are methods whose names are wrapped in double underscores. You usually do not call them directly: Python does it for you when you use an operator or a built-in function. Writing a == b turns into a.__eq__(b), a + b into a.__add__(b), and len(a) into a.__len__().
Thanks to them, your own objects behave like built-in types: results can be compared and sorted, points can be added with the + operator, and a course can be treated like a collection of lessons with len() and in. This kind of operator overloading makes sense when the meaning of the operation is obvious. Adding points is clear; adding two students probably is not.
When a method does not support the type of the second argument, it should return the special value NotImplemented instead of raising an exception. Python then tries the reverse operation on the other object (e.g. __radd__), and if that fails too, it raises a clear TypeError.
Watch out for equality and hashing: a class that defines __eq__ but not __hash__ can no longer be used as a dictionary key or a set element. The functools.total_ordering decorator fills in the missing <=, >, and >= comparisons based on __eq__ and __lt__.
Syntax
#class Name:
def __eq__(self, other): ...
def __lt__(self, other): ...
def __add__(self, other): ...
def __len__(self): ...
def __contains__(self, item): ...The most common special methods
#__eq__()
a == b
Equality. Without this method, an object is equal only to itself. The!=operator automatically returns the opposite result.__lt__()
a < b
The "less than" comparison. It is enough for sorting withsorted(), and formin()andmax(); Python handlesa > basb < a.__add__()
a + b
Addition. It should return a new object, andNotImplementedfor an unsupported type.__radd__()
b + a
Addition when the object is on the right side and the left argument cannot add it. Thanks to this,sum(), which starts from 0, works correctly.__len__()
len(a)
Length. It must return a non-negative integer. An object with a length of 0 is falsy in conditions.__contains__()
x in a
Checks whether an item belongs to the object. Without this method,inscans the items instead, if the object can be iterated over.__hash__()
hash(a)
The hash, needed in sets and dictionary keys. It must be based on the same fields as__eq__.
More examples
#class Points:
def __init__(self, value):
self.value = value
def __add__(self, other):
if isinstance(other, Points):
return Points(self.value + other.value)
if isinstance(other, int):
return Points(self.value + other)
return NotImplemented
def __radd__(self, other):
return self + other
def __repr__(self):
return f"Points({self.value})"
print(Points(20) + Points(15))
print(5 + Points(20))
print(sum([Points(10), Points(25), Points(5)]))
try:
Points(10) + "many"
except TypeError as error:
print(error)class Course:
def __init__(self, title, lessons):
self.title = title
self.lessons = list(lessons)
def __len__(self):
return len(self.lessons)
def __contains__(self, topic):
return topic.lower() in (lesson.lower() for lesson in self.lessons)
python = Course("Python", ["Variables", "Loops", "Functions"])
soon = Course("Coming soon", [])
print(len(python))
print("loops" in python, "classes" in python)
print(bool(python), bool(soon))class Badge:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return isinstance(other, Badge) and self.name == other.name
def __hash__(self):
return hash(self.name)
earned = {Badge("Start"), Badge("Streak 7"), Badge("Start")}
print(len(earned))
print(Badge("Streak 7") in earned)Without the __hash__ method, creating the set would raise TypeError, because a class with its own __eq__ loses the default hash.
Good practices
#- Comparison and arithmetic methods return
NotImplementedfor unsupported types instead of raising an exception. Python then tries the operation on the other object. - Arithmetic operators should create a new object. Changing
selfinside__add__would surprise anyone who uses+. - You get the full set of comparisons with the
functools.total_orderingdecorator, or through@dataclass(order=True).
Related entries
#- __str__() and __repr__()Methods that define an object’s text appearance: readable for the user (__str__) and technical for the programmer (__repr__).
- @dataclassA decorator that generates __init__, __repr__, and __eq__ for a data-storing class, based on type-annotated fields.
- len()Returns the number of items in a collection, or the number of characters in a string.
- sorted()Returns a new, sorted list built from the items of any collection.
- classDefines a class: a template for objects that combines data with the functions that act on it.
Spotted a mistake or missing an example? Write to us.