The Road To (d)Understanding.

A guide to understanding python's dunder methods

·

4 min read

The Road To (d)Understanding.

Dunder Methods - breaking it down.

Learning to code can be many things, rewarding, exciting, challenging, and often frustrating and confusing. As I continue my journey as a developer, I have often encountered situations where what I had intended to be a brief internet search, quickly became a rabbit hole of information that while undoubtedly important and even useful information was wildly out of the scope of both the work I was doing and my knowledge. Often leaving me more confused with my question only partially answered.

One concept that initially had me confused was the idea of Dunder methods. IIn this blog post, I aim to provide a beginner's guide that offers a brief and informative introduction to Dunder methods. The goal is to give those who are still at the beginning of their Python journey a solid foundation in the basics


Who, What, When, Where, Why and How


what is a Dunder Method? And when and where do we use them?

Dunder or "double underscore" methods are special types of methods named for the two underscores before and after their name. They are also known as "magic methods" or "special methods" for a good reason – they allow for the customization of Python classes, enabling developers to define how class instances react to various circumstances and operations. Dunder methods are primarily used when working with classes and data structures, making them an integral part of Python's key concept: Object Oriented Programming (OOP).

who, how and why: get to know some common Dunder Methods.

Python contains a great many Dunder methods, and all of them are extremely useful. In this post, I will explain several of the most widely and commonly used Dunder methods. A more comprehensive list of Dunder methods can be found: here

Meet: __init__ , __str__ , __repr__ , and __len__

  • When the __init__ method is used, it allows for a class instance to be initialized with specific attributes. Reminder the self passed as a parameter to each method refers to an individual instance of the class.

  • The __str__ and __repr__ methods are both used in the representation of various aspects of objects as a string. By defining these methods we can control the way objects are printed or converted into strings.

    • __str__ provides a more "human-friendly" representation designed for ease of readability.

    • __repr__ on the other hand is designed to provide a concise and unambiguous representation of the object

    • In other words __str__ prints end-user output and __repr__ prints information useful for debugging

  • The __len__ method allows for objects to have their length defined. In OOP, this is extremely useful as it can allow for the customization and creation of precise container objects such as lists or dictionaries.

class Person:
    def __init__(self, name):
        self.name = name
        self.items = []
    def __str__(self):
        return f"Person: {self.name}"
    def __repr__(self):
        return f"Person(name='{self.name}')"
    def __len__(self):
        return len(self.items)
-------------------------------------------------------------------------------
person = Person("Alice")
my_list = MyList()
my_list.items = [1, 2, 3, 4]
print(person.name)  # Output: Alice
print(str(person))  # Output: Person: Alice
print(repr(person)) # Output: Person(name='Alice')
print(len(my_list))  # Output: 4

MEET: __iter__ and __next__

  • These two methods are used in conjunction and allow for an object to be iterated using different types of loops.

  • __iter__ returns an 'iterator' for the object(self.current in the code below) allowing for the __next__ method to decide if and how the next item in the iteration can be retrieved.

class MyIterable:
    def __init__(self):
        self.items = []

    def __iter__(self):
        self.current = 0
        return self

    def __next__(self):
        if self.current >= len(self.items):
            #stop iteration is a terminating condition that helps 
            #prevent infinite iteration
            raise StopIteration
        item = self.items[self.current]
        self.current += 1
        return item

my_iterable = MyIterable()
my_iterable.items = [1, 2, 3, 4]

for item in my_iterable:
    print(item)  # Output: 1 2 3 4

One more note about underscores:

There is one more topic it is important to take note of when learning about Dunder methods, and that is just what exactly is going on when you see underscores popping up in your code. The following are several important rules regarding underscores it is important to keep in mind

  • Single Leading Underscore [ _username ]:

    • A single leading underscore conventionally indicates privacy.

    • It serves as a hint to other developers that the attribute or method is intended for internal use within the class or module.

  • Single Trailing Underscore

    • A single trailing underscore is used to avoid naming conflicts.

    • It differentiates variable names from Python keywords.

        def method(name, class='Classname'): #will return a syntax error
            ...
        def method(name, class_='Classname'): #will work just fine
      
  • Double Trailing Underscore

    • Double-leading underscores trigger name mangling, preventing accidental overriding of attributes in subclasses.

    • The attribute name gets prefixed with a single underscore and the class name.

Conclusion:

Congratulations on making it to the end of my introduction to Dunder methods, integral parts of creating and customizing sleek, elegant and flexible code. I hope that by reading what I have to say you were able to gain a more solid and concrete foundation on which to build your knowledge of Dunder methods, Python and coding in general.

References: