Getting things done with a ‘__dunder__’
By: Anand Puntambekar
Tags: Data Analytics, Dunder, Oops Concepts, Data Science, Python

Getting things done with a ‘__dunder__’

Summary of the most common __dunders__

You would have probably wondered at least once during some point in your python programming life: What in God’s name is going on with these __dunder__ things?!

Seeing a __dunder__ feels like:

Python has some functions that can be called at certain documented times. For instance, if Python detects ‘+’, then it will call the special function as __add__. These special methods are “__dunder__ methods”.

In this blog, I have tried to address and elaborate upon the most common __dunder__ methods.

(This blog is built majorly on the contents of this website)

1. __init__

The task of __init__ is to initialize the variables of the class, when an object of class is created.

# A Sample class with init method class Person:

# init method or constructor

def __init__(self, name):

self.name = name # Sample Method

def say_hi(self):

print('Hello, my name is', self.name) p = Person('Sachin')

p.say_hi() Output

Hello, my name is Sachin

2. __call__

When the instance of an object is used as a function, then __call__ is called.

class Product: def __init__(self):

print("Instance Created") # Defining __call__ method

def __call__(self, a, b):

print(a * b) # Instance created

ans = Product() # __call__ method will be called

ans(10, 20) Output

Instance Created

200

3. __getitem__ and __setitem__

__getitem__ and __setitem__ are used only in indexed attributes like arrays, dictionaries, lists etc. Instead of directly accessing and manipulating class attributes, they provide such methods, so that these attributes can be modified only by their own instances.

class Building():

def __init__(self, floors):

self._floors = [None]*floors

def __setitem__(self, floor_number, data):

self._floors[floor_number] = data

def __getitem__(self, floor_number):

return self._floors[floor_number]



building1 = Building(4) # Construct a building with 4 floors

building1[0] = 'Reception'

building1[1] = 'ABC Corp'

building1[2] = 'DEF Inc'

print( building1[2] ) Output:

DEF Inc

4. __getattr__ and __getattribute__

Python will call __getattr__ method whenever you request an attribute that hasn't already been defined.

class Count:

def __init__(self,mymin,mymax):

self.mymin=mymin

self.mymax=mymax def __getattr__(self, item):

self.__dict__[item]="X"

return 2 obj3 = Count(1,10)

print(obj3.mymin)

print(obj3.mymax)

print(obj3.mycurrent1)

print(obj3.mycurrent1) Output:

1

10

2

X

If you have __getattribute__ method in your class, Python invokes it for every attribute regardless of its existence.

class Count: def __init__(self,mymin,mymax):

self.mymin=mymin

self.mymax=mymax

self.current=None def __getattribute__(self, item):

if item.startswith(‘cur’):

print(“Variable not allowed”)

return “X”

else :

return super().__getattribute__(item)



# or you can use — -return super().__getattribute__(item) obj1 = Count(1,10)

print(obj1.mymin)

print(obj1.mymax)

print(obj1.current) Output:

1

10

Variable not allowed

X

5. __setattr__

__setattr__ is used to provide an alternate way of assigning the value of an object’s attribute.

# Python code to demonstrate

# working of setattr() # initializing class

class Gfg:

name = ‘GeeksforGeeks’



# initializing object

obj = Gfg() # printing object before setattr

print(“Before setattr name : “, obj.name) # using setattr to change name

setattr(obj, ‘name’, ‘Geeks’) # printing object after setattr

print(“After setattr name : “, obj.name) Output:

Before setattr name : GeeksforGeeks

After setattr name : Geeks

6. __del__

__del__ is a destructor method which is called as soon as all references of the object are deleted, i.e. when an object’s garbage is collected.

# Python program to demonstrate

# __del__ class Example:



# Initializing

def __init__(self):

print(“Example Instance.”) # Calling destructor

def __del__(self):

print(“Destructor called, Example deleted.”)



obj = Example()

del obj

7. __new__

__new__ is for object creation, unlike __init__ which is used for object initialization.

__new__ is invoked before __init__, as __new__ returns a new instance and __init__ is invoked after __new__ to initialize inner state.

class Vector2D(object):

def __new__(cls, *args, **kw):

print (“Testing new”)

return super(Vector2D,cls).__new__(cls) def __init__(self, x, y):

self.x = x

self.y = y



return None



vec = Vector2D(1,2)

print(vec) Output:

Testing new

<__main__.Vector2D object at 0x7f0bcc893b10>

8. __enter__ and __exit__

When the “with” statement is executed, Python evaluates the expression, calls the __enter__ method, and assigns whatever value __enter__ returns. Python will then execute the code body, and regardless of what happens in that code, will call the object’s __exit__ method.

class controlled_execution:

def __enter__(self):



return 100



def __exit__(self, type, value, traceback):

print (“Thing executed”) with controlled_execution() as thing:

print (thing*2) Output:

200

Thing executed

9. __len__

You can think of __len__ as a method that can be customized for any object. It is typically used foe calculating the size of an on object’s attribute.

class foo(object):

def __init__(self, obs=None):

if obs is None: # provide a default if no list was passed in.

obs = []



self._data_len = len(obs) def __len__(self):

return self._data_len



foo_obj = foo([1,2,3,4,5,6])

print (len(foo_obj)) Output:

6

10. __repr__ and __str__

Python’s __repr__ function returns the object representation. It could be any valid Python expression such as tuple, dictionary, string etc.

__str__ method returns the string representation of the object. This method is called when print() function or str() function is invoked on an object.