Dunder Methods
Tags: python
Published: 2019-12-28 23:39:37

Let us begin with an introduction to dunder methods, a foundation for everything python.

### The Magical Python

Much of python is magical, though one may only realize after using another programming language. Consider the following code:

```python
# make a list
lst = [0, 1, 2, 3]
# get the length of the list
len(lst)

# 4
```

Nothing strange about that, right? But in most common languages, you would do something like this:

```javascript
// make a list
let lst = [0, 1, 2, 3]
// get the length of the list
lst.length

// 4
```

The big difference is, that in other languages, length is an *attribute* of the list, which fits conceptually. But python makes `len(lst)` a separate function. Why?

The answer to this question is the key to advanced python programming: in python, the developer has the power to create classes that can *be treated as built-in objects* like lists. This can be done by implementing methods that are meant to be called by *python* not by python *code*.

To demonstrate this, let's write a class that will support the `len()` function.

### A Stack

Let's take a look at the humble `list`:

```python
lst = []
lst.append(0)
lst.append(1)
lst.append(2)

print(lst.pop())
# 2

print(lst.pop())
# 1

print(lst.pop())
# 0
```

As we can see, a python `list` has an `append` method to add to the end of the list, and `pop` to remove and return the last value in the list. But `list`s have other methods as well, we can access *any* item in the list, regardless of whether it is at the end:

```python
lst = [0, 1, 2, 3]
print(lst[2])

# 2
```

So, let's say that I need to be able to add values a list (`append`), and get back the most recently added value (`pop`). But I'm also concerned that no one accidentally retrieves any other value from the list. In this case, we can write a class that will only allow for pushing values onto the end of the list, and to pop them off. In computer science, this is known as a [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)).

```python
class Stack:
    def __init__(self):
        self._stack = []

    def append(self, item):
        self._stack.append(item)

    def pop(self):
        return self._stack.pop()
```

Now we can use the Stack:

```python
stack = Stack()
stack.append(0)
stack.append(1)
stack.append(2)

stack.pop()
# 2
stack.pop()
# 1
stack.pop()
# 0
```

Now we have a python list, that only has some of the methods of a list. But the methods that it *does* have are identical to a regular list, so we can simply swap one for the other (as long as no further methods are needed).

But there is a pitfall: what if at some point we need to check how many items are in the stack? We could take the naive approach, and add a length method:

```python
class Stack:
    def __init__(self):
        self._stack = []

    def append(self, item):
        self._stack.append(item)

    def pop(self):
        return self._stack.pop()

    def length(self):
        return len(self._stack)
```

But then we lose the advantage we had before: that we can use the `Stack` and `list` the same way.

The pythonic approach would be the following:

```python
class Stack:
    def __init__(self):
        self._stack = []

    def append(self, item):
        self._stack.append(item)

    def pop(self):
        return self._stack.pop()

    # The following method is a `dunder` or `magic` method
    def __len__(self):
        return len(self._stack)
```

Now we can do this:

```python
stack = Stack()
stack.append(0)
stack.append(1)
stack.append(2)

print(len(stack))
# 3
```

Just like a regular list.

### What is Going On?

When `len()` is called, what it does is more or less the following:

```python
def len(obj):
    return obj.__len__()
```

This is how *all* dunder methods work: they are called by python in response to something the developer wrote.

By using a function (`len`) to call the method (`__len__`), instead of just calling the method itself, developer will tend to use the *same method names as everyone else*. In the case of length, every class that allows you checking its size, will use `__len__`, because all python developers expect to be able to use `len`, which only works with `__len__`.

#### Up Next

Next we will talk about how dunder methods power python's `for` loops, using sequences and iterators.

Further reading:

+ Fluent Python by Luciano Ramalho (chapter 1).
+ [Metaobject](https://en.wikipedia.org/wiki/Metaobject) on Wikipedia.
+ [Data Model](https://docs.python.org/3/reference/datamodel.html) section of the [Python Language Reference](https://docs.python.org/3/reference/index.html).