Dunder methods

integrate object’s behaviour into python modus operandi

Methods in this page



__init__()

Init is called when an object is instantiated by calling the class. Here we define an initializer which has default arguments and test its behaviour.

class myDund:
    """Test class for dunder methods. Other dunder methods are added as the explanation goes"""
    
    def __init__(self, number: int=0 # an integer number
                 , text: str="nothing"): # a string
        self.number, self.text = number, text
        
test1 = myDund()
print(test1.number, test1.text)
test2 = myDund(42,"something")
print(test2.number, test2.text)
print(test1)
test2
0 nothing
42 something
<__main__.myDund object>
<__main__.myDund>

back to top

__new__()

Runs prior to initialization and is supposed to return the object that will be returned by the instantiation of the class.

Here we use it to control which class of object is created. A bit of boilerplate to get the classes’ names from __str__()

class baseTest:
    @classmethod
    def __repr__(cls):
        return cls.__name__
    
class Test(baseTest):
    def __repr__(self):
        return super().__repr__()
    __str__ = __repr__

class testA(Test):
    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)
        x = kwargs['x']
        
class testB(Test):
    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)
        y = kwargs['y']  
        
class testNew(Test):
    def __new__(cls, *args, **kwargs):
        if 'x' in [*kwargs]:
            obj = super(testNew,cls).__new__(testA, *args, **kwargs)
        elif 'y' in [*kwargs]:
            obj = super(testNew,cls).__new__(testB, *args, **kwargs)
        else:
            obj = super(testNew,cls).__new__(cls, *args, **kwargs)
        return obj
    
testNew1 = testNew(x=42)
testNew2 = testNew(y="42")
testNew3 = testNew()
print(f"{testNew1 = }\n{testNew2 = }\n{testNew3 = }")
testNew1 = testA
testNew2 = testB
testNew3 = testNew

back to top

__str__()

One thing to improve is the presentation of my object when printed or returned to the prompt. Str is called when print or str(built-in func, not obj method) is called on the object

def myStr(self: myDund):
    """Implementation of __str__()"""
    return f"({self.number}: {self.text})"

myDund.__str__ = myStr
print(test1)
str(test1), test1
(0: nothing)
('(0: nothing)', <__main__.myDund>)

back to top

__repr__()

Repr is used when __str__() is not implemented or in cases where you want to run eval on it, and should be implemented accordingly.

def myRepr(self: myDund):
    """Implementation of __repr__()"""
    return f"myDund(number={self.number}, text='{self.text}')"

myDund.__repr__ = myRepr
print(test1.__repr__())
test3 = eval(test2.__repr__())
test3
myDund(number=0, text='nothing')
myDund(number=42, text='something')

back to top

__eq__(), __lt__(), __gt__()

For personalized comparissons __eq__(), __lt__() and __gt__() can be used

myDund.__eq__ = lambda self, other: (self.number,self.text)==(other.number,other.text)
myDund.__lt__ = lambda self, other: (self.number,self.text)<(other.number,other.text)
myDund.__gt__ = lambda self, other: (self.number,self.text)>(other.number,other.text)
print(f"is {test3} equal to {test2}: \t{test3 == test2}")
print(f"is {test3} less than {test1}: \t{test3 < test1}")
print(f"is {test3} greater than {test1}: \t{test3 > test1}")
is (42: something) equal to (42: something):    True
is (42: something) less than (0: nothing):  False
is (42: something) greater than (0: nothing):   True

back to top

__len__()

Called by len() built-in method

def myLen(self: myDund) -> int:
    return len(self.text)
    
myDund.__len__ = myLen
len(test1), len(test2)
(7, 9)

back to top

__contains__()

Called by in built-in operator

def myCont(self: myDund, num: int) -> bool:
    return num in [self.number]
    
myDund.__contains__ = myCont
print(f"0 in {test1}: {0 in test1}")
0 in (0: nothing): True

back to top

__hash__()

Called by hash() built-in method

Hash generates a per run random number (if not int/float) for an object.

Hash calls __hash__() on an object if defined and truncates if representation is of higher bit width than host machine.

print(hash(1))
print(f"Hash for class {myDund.__name__}: {hash(myDund)}")
print(f"Hash for object {test2}, id {id(test2)}: {hash(test2)}")
print(f"Hash for object {test3}, id {id(test3)}: {hash(test3)}")
1
Hash for class myDund: 172505833084
Hash for object (42: something), id 2760109428944: 172506839309
Hash for object (42: something), id 2760109431584: 172506839474

The __hash__() method can then be overwritten for custom behaviour.

myDund.__hash__ = lambda self: hash(self.text)
print(f"Hash for class {myDund.__name__}: {hash(myDund)}")
print(f"Hash for object {test2}, id {id(test2)}: {hash(test2)}")
print(f"Hash for object {test3}, id {id(test3)}: {hash(test3)}")
Hash for class myDund: 172505833084
Hash for object (42: something), id 2760109428944: 6151054915300685838
Hash for object (42: something), id 2760109431584: 6151054915300685838

back to top

__call__()

Call when using the () operator.

See callable().

myDund.__call__ = lambda self: self.number*(self.text+" ")
test2()
'something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something '

back to top

__dir__()

Called by dir() built-in method.

“Without arguments, return the list of names in the current local scope. With an argument, attempt to return a list of valid attributes for that object.”

See dir().

myDund.__dir__ = lambda self: "this is inside"
print(dir(test1)) # returns as a sorted list
myDund.__dir__ = lambda self: ["this", "is", "inside"]
print(dir(test1)) # already a list, just sorts
[' ', ' ', 'd', 'e', 'h', 'i', 'i', 'i', 'i', 'n', 's', 's', 's', 't']
['inside', 'is', 'this']

back to top