Special methods

Methods with double underscores that let objects work with the ==, <, + and in operators, and with the len() function.

On this page

Example

#
Python
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]))
Resultsaved result, you can check it
True True
True True
[Score('Jack', 78), Score('Emma', 92), Score('Mia', 92)]
Score('Emma', 92)

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

#
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 with sorted(), and for min() and max(); Python handles a > b as b < a.
  • __add__()

    a + b

    Addition. It should return a new object, and NotImplemented for 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, in scans 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

#
Adding objects and sum()
Python
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)
Resultsaved result, you can check it
Points(35)
Points(25)
Points(40)
unsupported operand type(s) for +: 'Points' and 'str'
A custom collection: __len__ and __contains__
Python
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))
Resultsaved result, you can check it
3
True False
True False
Objects in a set: __eq__ together with __hash__
Python
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)
Resultsaved result, you can check it
2
True

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 NotImplemented for unsupported types instead of raising an exception. Python then tries the operation on the other object.
  • Arithmetic operators should create a new object. Changing self inside __add__ would surprise anyone who uses +.
  • You get the full set of comparisons with the functools.total_ordering decorator, or through @dataclass(order=True).

Related entries

#

Spotted a mistake or missing an example? Write to us.