You should define dunder methods on your classes in Python, but you shouldn't call dunder methods.
In Python, a method with two underscores around it (__like_this__) is referred to as a "dunder method", which stands for "double-underscore method".
As noted on my Python terminology page, the phrase "dunder method" isn't an official term.
Officially, dunder methods are referred to as "special methods", though that term doesn't sound "special" enough so folks sometimes say "dunder method". You'll also sometimes hear "magic method", though "magic method" seems to be less popular in recent years (after all, these methods aren't magical).
Dunder methods essentially act as a sort of "hook", allowing us (Python programmers) to customize the behavior of built-in Python behaviors.
Which dunder methods you should define will depend on the objects you're making.
If you're creating your own class without inheriting from another class, you'll almost certainly want these two dunder methods:
In fact, sometimes you'll want those even when you're inheriting from another class.
I also recommend considering implementing a __eq__ method to give your objects a nice sense of equality.
You might even want to allow < or > comparisons on your objects by implementing the various comparison operators.
If you decide that your object should support "tuple unpacking", you'll want to implement a __iter__ method
Beyond this, it's really up to the type of object you're making. Unless you're creating a context manager, a custom sequence, a custom mapping, or you're implementing some other "protocol" then you likely don't need to implement any other dunder methods. All the other dunder methods are fairly domain-specific.
Be sure not to go overboard though! Only implement dunder methods for operations your objects should support.
You can find a list of every dunder method in Python here.
You should implement dunder methods on your objects, but you shouldn't call dunder methods on other objects. Dunder methods should be implemented by us and called by Python.
Instead of calling a dunder method, it's usually better to use the higher level operations that are powered by those dunder methods.
For example, let's say we have a pathlib.Path object and a datetime.date object:
>>> from datetime import date
>>> from pathlib import Path
>>> path = Path.cwd()
>>> today = date.today()
If we want to convert these objects to strings, we could call the __str__ method on these objects:
>>> path_string = path.__str__()
>>> date_string = today.__str__()
>>> path_string
'/home/trey'
>>> date_string
'2024-01-17'
But we're not supposed to do that!
We're meant to pass those objects to the built-in str function:
>>> path_string = str(path)
>>> date_string = str(today)
The same applies for every other dunder method.
We should use the len function to get an object's length, not the __len__ method:
>>> my_collection.__len__() # no
>>> len(my_collection) # yes
To check the attributes on an object, we should use the dir function instead of calling the object's __dir__ method:
>>> my_object.__dir__() # no
>>> dir(my_object) # yes
To get the next item in an iterator, we should use the next function instead of calling the __next__ method:
>>> my_iterator.__next__() # no
>>> next(my_iterator) # yes
And to check whether one object contains another, we should use the in operator instead of calling the __contains__ method:
>>> my_object.__contains__(thing) # no
>>> thing in my_object # yes
But why?
Why should we avoid calling dunder methods?
Dunder methods are low-level implementation details that the Python interpreter uses to power various Python features. The operators, functions, and features they power are the high-level tools that us Python programmers are meant to use.
So you shouldn't call dunder methods because the high-level feature(s) they power are what we're meant to use instead.
But there's another reason you shouldn't call dunder methods: there's not a one-to-one relationship between dunder methods and the features they empower. Sometimes calling dunder methods won't be nearly as straightforward as using a corresponding higher-level Python feature.
For example, if we want to divide one number by another, we could use the / operator or we could use the __truediv__ method:
>>> n = 10
>>> m = 2
>>> n / m
5.0
>>> n.__truediv__(m)
5.0
But while the / operator will always give us the expected answer, the __truediv__ will not:
>>> m = 2.5
>>> n / m
4.0
>>> n.__truediv__(m)
NotImplemented
Integers don't know how to interact with floating point numbers, so when using the / operator between an integer and a floating point number, Python needs to ask the floating point number to divide the integer by itself.
It does this by using its __rtruediv__ method:
>>> m.__rtruediv__(n)
4.0
I won't go so far as to say "don't ever call dunder methods". But I would strongly discourage calling a dunder method if there's another way to accomplish the same task.
You may sometimes find value in calling a dunder method.
For example, if you're overriding a dunder method, you may feel the need to use super to call the same dunder method in a parent class:
class MyDefaultDict(dict):
def __getitem__(self, key):
if key not in self:
self[key] = ""
return super().__getitem__(key)
d = MyDefaultDict()
assert d[4] == ''
assert d == {4: ''}
Using super()[key] won't work because the super class doesn't implement a __getitem__ method, but looking up the __getitem__ attribute on a super() object does work (assuming a parent class implements that method).
I recommend that Python programmers avoid calling dunder methods because:
Whenever I see a dunder method called within code, I think "something low-level is going on here". That's absolutely necessary sometimes, but most of the time you should be able to use the equivalent high-level operation instead.
So you should define dunder methods on your objects, but let Python do the dunder method calling. Dunder methods are for us to define and for Python to call.
If you're curious about which dunder methods do what, see every dunder method in Python.
Need to fill-in gaps in your Python skills?
Sign up for my Python newsletter where I share one of my favorite Python tips every week.
Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.
Sign in to your Python Morsels account to track your progress.
Don't have an account yet? Sign up here.