Thanks to visit codestin.com
Credit goes to www.pythonmorsels.com

Don't call dunder methods

Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
5 min. read Python 3.10—3.14
Share
Copied to clipboard.

You should define dunder methods on your classes in Python, but you shouldn't call dunder methods.

What is a dunder method?

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.

Define dunder methods on your classes

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:

  • __init__: the initializer method
  • __repr__: the developer-focused string representation

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.

Don't call dunder methods on your objects

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?

Why not call 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

Sometimes you need to call dunder methods

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).

Avoid calling dunder methods, but do define them

I recommend that Python programmers avoid calling dunder methods because:

  1. There's not always a one-to-one relationship between high-level operations and individual dunder methods
  2. Dunder method calls signals that something low-level is happening
  3. There's usually a higher-level way to do the same thing that looks friendlier

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.

A Python Tip Every Week

Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.