why are they needed and what can

Absolutely all Pythonists eventually stumble over double underscore methods. Why are they needed at all? Why not do without them? Who came up with this? What is the difference from classic methods without underscores? Why aren’t they everywhere? If you’re wondering the same thing, here’s an overview of some of the most popular double underscore techniques. In Python, they define custom properties and behavior of objects.

INSERT. It is worth distinguishing them from IPython magic methods that make working in Google Colaboratory / Jupyter Notebook more convenient. If you want to take a closer look at the latter, check out my article Top Most Useful Magic Commands for Python Regulars.

For example, when you perform operations such as addition (+), subtraction (-), multiplication

and division (/), Python automatically calls the appropriate “magic” methods (__add__, __sub__, __mul__, and __div__ respectively).

The word “dunder” here is not a curse word, it refers to the double underscore. The two underscores are only there to prevent name conflicts with other methods implemented by unsuspecting programmers. And they were introduced, of course, by the creator of the language, Guido van Rossum.


But who needs these Dunder methods? To understand why this class of methods is separated into a separate group, consider an example fromthreads on Stack Overflow

. User Greg Beech asks why such complications are needed at all, if instead of __len__ you can just use len()? A wonderful snippet from Mangu Singh Rajpurohit answers this question once and for all, in my opinion:

    dict1 = {1 : "ABC"}
dict2 = {2 : "EFG"}

dict1 + dict2

“Imagine you have two dictionaries and you want to add them:

    TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

Executing this code will throw an error:

    class AddableDict(dict):

    def __add__(self, otherObj):
        self.update(otherObj)
        return AddableDict(self)


dict1 = AddableDict({1 : "ABC"})
dict2 = AddableDict({2 : "EFG"})

print (dict1 + dict2)

The TypeError says that dictionaries cannot be added. But we really need it! So let’s add this functionality to the dictionary class. More precisely, we initiate the child AddableDict class:

    {1: 'ABC', 2: 'EFG'}

And voila! Dictionaries are added up:

Some interesting Dunder methods

1. __init__:

    class Square:
    def __init__(self, side_length):
        """
        Чтобы создать квадрат (Square), нам нужно знать длину его         стороны, чтобы позднее передать это значение как аргумент:        Square(1). Чтобы убедиться, что сущность знает свою длину         стороны, сохраним ее так:
        """
        self.side_length = side_length

sq = Square(1)

If you have certain classes in Python, you will definitely come across an __init__ method. It is responsible for initializing the instance of the class, so this is where you usually set its essential attributes – for example, the length of the edge of the square:

2. __call__:

    class CallableObject: 
    def __call__(self, x): 
        return x ** 2 
    
    callable_object = CallableObject() 
    print(callable_object(5)) # выводит 25

This method allows you to create so-called “callable” objects, that is, they can be called as functions.

3. __getitem__ and __setitem__:

    class MyDictionary:
    # выводит "value"
    def __init__(self): self.dictionary = {} 
    def __getitem__(self, key): return self.dictionary[key] 
    def __setitem__(self, key, value): self.dictionary[key] = value 


my_dict = MyDictionary() 
my_dict["test"] = "value" 
print(my_dict["test"])

Methods allow accessing an object by index or key, as if it were a list or dictionary.

4. __iter__ and __next__:

    class IterableObject:
    def __init__(self, max): 
        self.max = max 
        self.current = 0
        

def __iter__(self): 
    return self 


def __next__(self): 
    if self.current < self.max: 
        result = self.current 
        self.current += 1 
        return result 
    else: 
        raise StopIteration 

iterable_object = IterableObject(5) 
for i in iterable_object:      print(i)  # выводит числа от 0 до 4

These methods allow you to create iterables that can be used in a for loop.

5. __enter__ and __exit__:

    class MyContextManager: 
    def __init__(self): 
        print("Инициализируем блок") 
        
    def __enter__(self): 
        print("Входим в блок") 
        return self 
    
    def __exit__(self, exc_type, exc_val, exc_tb): 
        print("Выходим из блока") 
with MyContextManager():              pass

These methods are used to create context managers (that is, objects that can be used in a with block) and allow you to control the resources that need to be freed after use.

When entering and exiting the with block, the corresponding messages will be displayed.

And a little cheat sheet


Here is a list of simple methods that may come in handy if you are generally ready to let Dunders into your code:

Conclusion

To be honest, it took me more than one year to understand and accept the concept of Dunder methods. Due to information overload, the brain has often sought to avoid additional complexity – and magical methods just fell into this category. Only having exhausted in practice the classical possibilities of certain objects, I returned to this topic with the help of StackOverflow.

So do not reproach yourself if you want to see this topic at first and immediately forget it: not everything in Python is suitable for beginners, some things require experience and demand. But by getting used to such a cool feature (and a dozen more of them), you will gain an advantage in the competition for the most interesting projects, better working conditions and a larger offer.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *