DEV Community

Cover image for What are these __method__ in 🐍 you sssssay? Look no further!
Hamzza K
Hamzza K

Posted on

What are these __method__ in 🐍 you sssssay? Look no further!

Have you ever used or seen the __init__() or __str__() method in Python classes? These are called dunder methods! Dunder, short for "double underscore", is a term used in Python to refer to special methods that allow programmers to modify the behavior of objects in a logical and intuitive way. In this article, we will examine Python's dunder methods and show how to use them to create more Pythonic code. We'll cover the basics of dunder methods, their common use cases, and best practices for using them effectively.

How do Dunder Methods work?

Dunder (double underscore) methods, are unique in the Python programming language that have double underscores defined on either side of their names, such as __init__(), __str__(), or __eq__(). These methods allow you to modify the behaviour of objects and give a more user-friendly interface for dealing with them. You can specify how instances of the class should act when they are generated, printed, compared, or utilised in arithmetic operations by specifying dunder methods in a class declaration.

Common Use Cases for Dunder Methods

Dunder methods are extensively used in Python libraries and frameworks, and have become a standard convention in the Python community. Here are some common use cases for dunder methods:

Initialization

The __init__() method is used to initialize a new instance of a class. It is called when a new object is created, and is used to set the initial state of the object. This method takes a self parameter, which refers to the instance of the class being initialized, and any additional parameters that are needed to set the initial state of the object. The __init__ method is most commonly used to initialize the attributes of an object when it is created.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

person = Person("John", 25)
print(person.name) # Output: Alice
print(person.age) # Output: 25
Enter fullscreen mode Exit fullscreen mode

String Representation

The __str__() and __repr__() methods serve the purpose of defining a string representation of an object.

While __str__() is used to define a human-readable string representation, __repr__() is used to define a machine-readable format for the object.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"{self.name} ({self.age} years old)"

person = Person("John", 25)
print(person) # Output: John (25 years old)
Enter fullscreen mode Exit fullscreen mode
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return f"Person(name='{self.name}', age={self.age})"

person = Person("John", 25)
print(person) # Output: Person(name='John', age=25)
Enter fullscreen mode Exit fullscreen mode

The __init__ method can be used in scenarios involving multiple inheritance to initialize attributes of all parent classes.

class Animal:
    def __init__(self, species):
        self.species = species

    def speak(self):
        pass

class Mammal:
    def __init__(self, mammal_type):
        self.mammal_type = mammal_type

    def feed_young(self):
        pass

class Dog(Animal, Mammal):
    def __init__(self, name, age):
        Animal.__init__(self, "Dog")
        Mammal.__init__(self, "Canine")
        self.name = name
        self.age = age

    def speak(self):
        return "Woof"

dog = Dog("Fido", 3)
print(dog.species) # Output: Dog
print(dog.mammal_type) # Output: Canine
print(dog.name) # Output: Fido
print(dog.age) # Output: 3
print(dog.speak()) # Output: Woof
Enter fullscreen mode Exit fullscreen mode

Container Operations

Dunder methods can be used to define the behavior of container operations such as len(), indexing ([]), assignment to an indexed value (obj[index] = value), deletion of an indexed value (del obj[index]), and membership testing (in)

  • __len__()

    The __len__() method is used to define the behavior of the len() function for objects of a class. It should return the number of items in the container.

class MyList:
    def __init__(self, items):
        self.items = items

    def __len__(self):
        return len(self.items)

my_list = MyList([1, 2, 3])
print(len(my_list)) # Output: 3
Enter fullscreen mode Exit fullscreen mode
  • __getitem__()

The __getitem__()
 method is used to define the behavior of indexing ([]) for objects of a class. It should return the item at the given index.

class MyList:
    def __init__(self, items):
        self.items = items

    def __getitem__(self, index):
        return self.items[index]

my_list = MyList([1, 2, 3])
print(my_list[2]) # Output: 3
Enter fullscreen mode Exit fullscreen mode
  • __setitem__()

The __setitem__() method is used to define the behavior of assignment to an indexed value (obj[index] = value) for objects of a class. It should set the item at the given index to the given value.

class MyList:
    def __init__(self, items):
        self.items = items

    def __setitem__(self, index, value):
        self.items[index] = value

my_list = MyList([1, 2, 3, 4, 5])
my_list[2] = 66
print(my_list.items) # Output: [1, 2, 66, 4, 5]
Enter fullscreen mode Exit fullscreen mode
  • __delitem__()

The __delitem__() method is used to define the behavior of deletion of an indexed value (del obj[index]) for objects of a class. It should delete the item at the given index.

class MyList:
    def __init__(self, items):
        self.items = items

    def __delitem__(self, index):
        del self.items[index]

my_list = MyList([1, 2, 3, 4, 5])
del my_list[2]
print(my_list.items) # Output: [1, 2, 4, 5]
Enter fullscreen mode Exit fullscreen mode
  • __contains__()
  • The __contains__() method is used to define the behavior of membership testing (in) for objects of a class. It should return True if the given value is present in the container, and False otherwise.
class MyList:
    def __init__(self, items):
        self.items = items

    def __contains__(self, value):
        return value in self.items

my_list = MyList([1, 2, 3, 4, 5])
print(2 in my_list) # Output: True
print(10 in my_list) # Output: False
Enter fullscreen mode Exit fullscreen mode

These examples demonstrate how container operations can customize the behavior of Python objects. By defining these methods in a class definition, you can provide a more intuitive interface for interacting with objects and make your code more flexible and customizable.

Comparison

Dunder methods can be used to define the behavior of comparison operators such as ==<><=, and >= for objects of a class. These methods should return True or False depending on whether the comparison is true or false.

  • __eq__()

The __eq__() method is used to compare two objects for equality.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        return self.name == other.name and self.age == other.age

person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
person3 = Person("Alice", 25)

print(person1 == person2) # Output: False
print(person1 == person3) # Output: True

Enter fullscreen mode Exit fullscreen mode
  • __lt__()

The __lt__() method is used to compare two objects to check if one is less than the other.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __lt__(self, other):
        return self.age < other.age

person1 = Person("Alice", 25)
person2 = Person("Bob", 30)

print(person1 < person2) # Output: True
print(person2 < person1) # Output: False

Enter fullscreen mode Exit fullscreen mode
  • __gt__()

The __gt__() method is used to compare two objects to check if one is greater than the other.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __gt__(self, other):
        return self.age > other.age

person1 = Person("Alice", 25)
person2 = Person("Bob", 30)

print(person1 > person2) # Output: False
print(person2 > person1) # Output: True

Enter fullscreen mode Exit fullscreen mode
  • __le__()

The __le__() method is used to compare two objects to check if one is less than or equal to the other. Here's an example:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __le__(self, other):
        return self.age <= other.age

person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
person3 = Person("Charlie", 25)

print(person1 <= person2) # Output: True
print(person2 <= person1) # Output: False
print(person1 <= person3) # Output: True

Enter fullscreen mode Exit fullscreen mode
  • __ge__()

The __ge__() method is used to compare two objects to check if one is greater than or equal to the other. Here's an example:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __ge__(self, other):
        return self.age >= other.age

person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
person3 = Person("Charlie", 25)

print(person1 >= person2) # Output: False
print(person2 >= person1) # Output: True
print(person1 >= person3) # Output: True

Enter fullscreen mode Exit fullscreen mode

Arithmetic Operations

Dunder methods can be used to define the behavior of arithmetic operators such as +-*, and / for objects of a class. These methods should return a new object that represents the result of the arithmetic operation.

  • __add__()

The __add__() method is used to define the behavior of the + operator when applied to two objects. Here's an example:

    class Vector:
        def __init__(self, x, y):
            self.x = x
            self.y = y

        def __add__(self, other):
            return Vector(self.x + other.x, self.y + other.y)

    v1 = Vector(1, 2)
    v2 = Vector(3, 4)
    v3 = v1 + v2

    print(v3.x) # Output: 4
    print(v3.y) # Output: 6

Enter fullscreen mode Exit fullscreen mode
  • __sub__()

The __sub__() method is used to define the behavior of the - operator when applied to two objects. Here's an example:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v2 - v1

print(v3.x) # Output: 2
print(v3.y) # Output: 2

Enter fullscreen mode Exit fullscreen mode
  • __mul__()

The __mul__() method is used to define the behavior of the * operator when applied to two objects. Here's an example:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __mul__(self, other):
        return Vector(self.x * other, self.y * other)

v1 = Vector(1, 2)
v2 = v1 * 3

print(v2.x) # Output: 3
print(v2.y) # Output: 6

Enter fullscreen mode Exit fullscreen mode
  • __truediv__()

The __truediv__() method is used to define the behavior of the / operator when applied to two objects. Here's an example:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __truediv__(self, other):
        return Vector(self.x / other, self.y / other)

v1 = Vector(3, 6)
v2 = v1 / 3

print(v2.x) # Output: 1.0
print(v2.y) # Output: 2.0
Enter fullscreen mode Exit fullscreen mode

Best Practices for Using Dunder Methods

While dunder methods can be a powerful tool for customizing the behavior of objects in Python, it is important to use them thoughtfully and appropriately. Here are some best practices for using dunder methods effectively:

  • Use dunder methods to define object behavior in a way that is consistent with Python conventions and libraries.
  • Use descriptive names for dunder methods that accurately reflect their purpose and behavior.
  • Be aware of the specific names and behaviors of dunder methods used by Python itself, and use them appropriately when defining your own dunder methods.
  • Use dunder methods judiciously. Too many can make your code more complex and harder to understand.
  • Thoroughly test your dunder methods to ensure that they behave as expected and do not introduce unexpected side effects.

Conclusion

Dunder methods are a powerful feature of Python that enables developers to customize the behavior of objects in a consistent and intuitive way. By using dunder methods effectively, you can make your code more readable, maintainable, and flexible. You can also ensure that it is consistent with the expectations of other Python developers. Understanding the usage of dunder methods is an important part of writing Pythonic code, and can help you become a more effective Python developer.

Top comments (0)