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.
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:
__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:
__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
Let’s take the example of string comparison in two languages: Java & Python
In Python, we’d do:
a = "hello!"
In Java, we’d do:
String a = "hello!";
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
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,
Neat! But what about…
print(num == val) # num.__eq__(val) => ???
I’ll let you think about that. If you want, keep reading, or skip to this section for the answer.
Apart from special notation, dunder methods also enable us to use builtin functions with little thought. We’ve already seen the
Try this out:
print(num == val)
NotImplemented. Interesting. There’s more going on here.
Well, dunders are more than just shorthands. In this case, Python evaluates
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
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”.
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.
Depending on the context,
__name__ refers to slightly different things, but always refer to some sort of name.
Finally, we get back to
__name__ == "__main__".
Now, when we run
python B.py, what we get is:
A: My __name__ is A
__main__ if it was run as the main file! Makes sense. When a file is imported, it’s
__name__ is simply the file name.
__name__ == "__main__", we can run code selectively based on if the file is being imported, or ran directly.