What are Dunder Methods?

Related article:

Transcript:

Let's talk about how third-party library authors use dunder methods to make their Python objects feel like native Python objects.

Operator Overloading Relies on Dunder Methods

Let's add two variables, x and y:

>>> x = 2
>>> y = 3
>>> x + y
5

When Python executed this code, it called the __add__ method on x, passing it y:

>>> x.__add__(y)
5

The method __add__ is pronounced "dunder add", dunder as in double underscore because it starts and ends with two underscores.

If we look for help on x (which points to an integer) we'll see there are lots of dunder methods (all those methods named __something__):

>>> help(x)
Help on int object:

class int(object)
 | ...
 |
 |  Methods defined here:
 |
 |  __abs__(self, /)
 |      abs(self)
 |
 |  __add__(self, value, /)
 |      Return self+value.
 |
 |  __and__(self, value, /)
 |      Return self&value.
 |
 |  __bool__(self, /)
 |      self != 0
 |
 | ...

A dunder method acts as a contract between the Python interpreter and the person who made a particular Python class.

The person who made the int class defined the __add__ method to let Python know that the + symbol works on int objects.

Addition relies on a dunder method, but so do many other operations. In fact, when you try to ask if two things are equal, that uses the __eq__ method. Even equality relies on dunder methods in Python!

Manually Calling a Dunder Method

Dunder methods are a contract between the person who made the class, and Python itself. As Python programmers, we're not supposed to call dunder methods directly. Instead, we're meant to use the high-level operations (like the + and == operators) that rely on those dunder methods.

Let's see an example of why we're not supposed to call dunder methods directly.

If we check for equality between an integer and a floating point number by manually calling __eq__, we'll see NotImplemented:

>>> y = 3
>>> z = 3.0
>>> y.__eq__(z)
NotImplemented

NotImplemented is a special value that tells Python, as an integer, I don't know how to check myself for equality with a floating-point number.

So Python then has to ask z, the floating-point number, "are you equal to the y, the integer?":

>>> z.__eq__(y)
True

It says True.

That's the reason y == z actually works:

>>> y == z
True

We're supposed to do things (like equality check here), using the == operator. We're not supposed to use the dunder methods directly. It's Python's job is to call the dunder methods; our job is to do the high-level operation instead.

Dunder Methods Power Some Built-in Functions

Dunder methods in Python power operators like +, -, * and ==. They also power some of the built-in functions.

For example, if you ask for the length of something:

>>> name = "Trey"
>>> len(name)
4

That relies on the __len__ method:

>>> name.__len__()
4

The len function is a built-in function (as opposed to a method) because Python has decided that having a length is a pretty fundamental property of an object. So Python has a built-in function called len that delegates to a dunder method which is used for discovering whether something has length and what that length is.

Having a length is all about having a __len__ method:

>>> numbers = [2, 1, 3, 4, 7]
>>> numbers.__len__()
5
>>> len(numbers)
5

In fact, many of the things that seem, completely fundamental to Python, like indexing a list, are powered by dunder methods.

>>> numbers[0]
2

Whenever you use square brackets on an object in Python, the __getitem__ method is called:

>>> numbers.__getitem__(0)
2

Summary

So dunder methods, power pretty much all the operators in Python and even some of the built-in functions.

A dunder method is a method (that Python knows about), which has two underscores before it and two underscores after it.

It's a contract between the person who implemented a certain class and the Python interpreter, which knows when to call that particular dunder method.

If you make your own classes in Python, you can use dunder methods for operator overloading. And that's actually what third party library authors do, in order to make the objects in their libraries act like native Python objects.