# Python __dunders__

#### What they are, why they're used and why they're great.

This post is a work in progress.

## What’s a __dunder__?

If you’ve used python for a while, you’ll probably have heard the term dunder thrown around a lot. dunder init, dunder main and dunder name are some of the more common dunders. You might, at one point have seen, or even been told to do:

The name “dunder”, as you might have figured out by now, is a short-form of double underscore, the characters surrounding these names. However, they’re more than just just a naming convention. They’re also known as magic methods, because as you’ll see in a while, they’re magic.

## Dunder methods

### __init__

This is likely one of the first dunder methods you’ll come across. It can be thought of as analogous to a constructor in languages like C++ and Java, though pedantically it’s not a constructor (that would be __name__), rather it’s an initializer. The British are wrong.

While I won’t go into the details of a python object lifecycle, the gist of its:

graph LR; new(__new__) --> init(__init__) --> do(do whatever) --> del(__del__);

__new__ creates the object and passes it along to __init__. At this stage, the object has no attributes. __init__‘s job is to fix that. __init__ gives this new object properties and attributes. After this, your code does whatever it wants with the object. At the end of it all, we say goodbye. __del__ is run right before the object is deleted.

We’ll talk about __main__ later :wink:

Notice that __init__ is the method that gives v the attribute val. It initialized an empty object with the attributes for its type. However, this object was not created here, rather it was created in __new__.

### A shorthand

Let’s take the example of string comparison in two languages: Java & Python

In Python, we’d do:

In Java, we’d do:

Focus on the equality statement. In python, we did a short == to compare the two objects. In Java, we did .equals. Here’s the thing: we also did .equals in python, just indirectly. What’s happened is that python sees the statement a == b, and converts it into the equivalent statement a.__eq__(b)

In a similar fashion, the following dunders are called during the following operations (non-exhaustive):

Implementing the appropriate dunder in self‘s class enables us to use these neater shorthands. For example,

### Builtin Support

Apart from special notation, dunder methods also enable us to use builtin functions with little thought. We’ve already seen the

### More than a shorthand

Try this out:

It prints False, then NotImplemented. Interesting. There’s more going on here.

Well, dunders are more than just shorthands. In this case, Python evaluates num.__eq__(val) into NotImplemented. After all, int is a builtin, and Value is our own class. How is Python supposed to know how to compare them? Note that it didn’t raise the error, it returned the error type. Now what? Well, it inferred that a == b should be the same as b == a. It then re-runs the function call, but this time on val.__eq__(num). This time, as expected, it returns False, hence the expression num == val is False.

graph TD; eq(a == b) --> dunder{{"a.__eq__(b)"}} dunder -- success --> s1(return value) dunder -- NotImplemented --> t1{{"b.__eq__(a)"}} t1 -- success --> s2(return value) t1 -- NotImplemented --> s3("return False") style s3 stroke-width:2px,fill:none,stroke:red; style s1 stroke-width:2px,fill:none,stroke:green; style s2 stroke-width:2px,fill:none,stroke:green;

See? Magic! This behavior can also be seen with a >= b becoming b <= a, a > b to b < a and vice versa. Note that a >= b does not get inferred to be equal to not (b < a), as there are use cases where both are true, or both false. Case in point, nan. In general, this behavior will be seen when operations can be “flipped”.

## Dunder variables

There are a few ubiquitous dunder variables. These follow the familiar double underscore syntax but aren’t special methods of a class. Rather, they refer to special attributes. Again, this list is non-exhaustive and only serves as a taste of what dunder variables are out there.

### __name__

Depending on the context, __name__ refers to slightly different things, but always refer to some sort of name.

#### Modules

Finally, we get back to __name__ == "__main__".

Now, when we run python B.py, what we get is:

__name__ is __main__ if it was run as the main file! Makes sense. When a file is imported, it’s __name__ is simply the file name.

By checking __name__ == "__main__", we can run code selectively based on if the file is being imported, or ran directly.