pythoninstantiationparadigms

Dynamic call to attribute's method ends up to calling it on wrong instance (after reseting attribute)


I'm encountering a behavior that surprises me on dynamic calls:

I have a class that acts as an attribute for another class. When the container class resets this attribute, the methods of the attribute class are applied to the old instance if called dynamically, and to the new instance if called directly.

What must be done to ensure that dynamic calls are made to an attribute that has been reset?

class attributeClass():
    def __init__(self):
        self.dummy = 0
    
    def printDummy(self):
        print('id : '+hex(id(self)))
        print(self.dummy)
    
    def modifyDummy(self, newValue):
        self.dummy = newValue



class ContainerClass():
    def __init__(self):
        self.attributeObject = attributeClass()
        self.dic = {'dynamicCall': self.attributeObject.printDummy }

    def resetAttribute(self):
        self.attributeObject = attributeClass()



def main():
    containerObject = ContainerClass() # create instance of ContainerClass
    print('--A--: create instance of ContainerClass')
    containerObject.attributeObject.printDummy() # call printDummy directly 
    containerObject.dic['dynamicCall']() # call printDummy dynamically 

    print('--B--: call to modifyDummy(5)')
    containerObject.attributeObject.modifyDummy(5) # change the dummy value
    containerObject.attributeObject.printDummy() # direct call
    containerObject.dic['dynamicCall']() # dynamic call

    print('--C--: call the reset function')
    containerObject.resetAttribute() # reset the container's attribute "attributeClass"
    containerObject.attributeObject.printDummy() # direct
    containerObject.dic['dynamicCall']() # dynamic



if __name__ == '__main__': 
    main()

the above code has as output (addresses may vary):

--A--: create instance of ContainerClass

id : 0x22ea3ad6ba0

0

id : 0x22ea3ad6ba0

0

--B--: call to modifyDummy(5)

id : 0x22ea3ad6ba0

5

id : 0x22ea3ad6ba0

5

--C--: call the reset function

id : 0x22ea3d78b90

0

id : 0x22ea3ad6ba0

5


Solution

  • It's because you cached a bound method. The bound method self.attributeObject.printDummy is an object which has a reference to the class instance self.attributeObject. See the reference Method Objects.

    You can avoid caching it in various ways. One example is to use a lambda like the following.

    class ContainerClass():
        def __init__(self):
            self.attributeObject = attributeClass()
            self.dic = {'dynamicCall': lambda: self.attributeObject.printDummy() }