Let us begin with an introduction to dunder methods, a foundation for everything python.
Much of python is magical, though one may only realize after using another programming language. Consider the following code:
# 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:
// 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
Let's take a look at the humble
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
lists have other methods as well, we can access any item in the list, regardless of whether it is at the end:
lst = [0, 1, 2, 3] print(lst) # 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.
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:
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:
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
list the same way.
The pythonic approach would be the following:
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:
stack = Stack() stack.append(0) stack.append(1) stack.append(2) print(len(stack)) # 3
Just like a regular list.
len() is called, what it does is more or less the following:
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
Next we will talk about how dunder methods power python's
for loops, using sequences and iterators.