Python: replacing a function within a class of a module

25,313

Solution 1

I suggest 4 solutions, from the worst to the best (IMHO), but of course it also depends on your specific constraints:

  1. Replace the instance method (1): I use the fact that functions are descriptors in Python, so that I can use the __get__ method on AlternativeFunc to get it as a method of the instance mytest and overwrite the testFunc method of the instance mytest (without overwriting the class method):

    class testMOD(object):
        def testFunc(self, variable):
            var = variable
            self.something = var + 12
            print('Original:', self.something)
    
    def alternativeFunc1(self, variable):
        var = variable
        self.something = var + 1.2
        print('Alternative1:', self.something)
    
    mytest1 = testMOD()
    mytest1.testFunc(10)   # Original: 22
    
    mytest1.testFunc = alternativeFunc1.__get__(mytest1, testMOD)
    mytest1.testFunc(10)   # Alternative1: 11.2
    mytestX = testMOD()
    mytestX.testFunc(10)   # Original: 22
    
  2. Replace the instance method (2): This time, I use types.MethodType which is a bit more readable than the first solution:

    import types
    
    class testMOD(object):
        def testFunc(self, variable):
            var = variable
            self.something = var + 12
            print('Original:', self.something)
    
    def alternativeFunc1(self, variable):
        var = variable
        self.something = var + 1.2
        print('Alternative1:', self.something)
    
    mytest1 = testMOD()
    mytest1.testFunc(10)   # Original: 22
    
    funcType = types.MethodType
    mytest1.testFunc = funcType(alternativeFunc1, mytest1)
    mytest1.testFunc(10)   # Alternative1: 11.2
    mytestX = testMOD()
    mytestX.testFunc(10)   # Original: 22
    
  3. Perform a monkey patching of the class method. Differently from the first method, it changes the behavior of any instance of the class:

    class testMOD(object):
        def testFunc(self, variable):
            var = variable
            self.something = var + 12
            print('Original:', self.something)
    
    def alternativeFunc2(self, variable):
        var = variable
        self.something = var + 1.2
        print('Alternative2:', self.something)
    
    mytest2 = testMOD()
    mytest2.testFunc(10)   # Original: 22
    
    testMOD.testFunc = alternativeFunc2
    mytest2.testFunc(10)   # Alternative2: 11.2
    mytestX = testMOD()
    mytestX.testFunc(10)   # Alternative2: 11.2
    
  4. Create a class inherited from testMOD to override the method:

    class testMODNew(testMOD):
         def testFunc(self, variable):
             var = variable
             self.something = var + 1.2
             print('Alternative3:', self.something)
    
    mytest3 = testMODNew()
    mytest3.testFunc(10) # Alternative3: 11.2
    

Solution 2

You can monkey patch this method as follows:

class TestMOD(object):

    def testFunc(self, variable):
        var = variable
        self.something = var + 12
        print(f'original {self.something}')


def alternativeFunc(self, variable):
    var = variable
    self.something = var + 1.2
    print(f'alternative {self.something}')


if __name__ == '__main__':

    test_original = TestMOD()
    test_original.testFunc(12)

    TestMOD.testFunc = alternativeFunc

    test_alternate = TestMOD()
    test_alternate.testFunc(12)

Output:

original 24
alternative 13.2

Solution 3

Check class inheritance in Python to create your own custom class:

from somemodule import TestMOD

class YourCustomClass(TestMOD):

    # change the function
    def test_func(self, variable):
        #
        #

your_class = YourCustomClass()
your_class.test_func(x)

Solution 4

This is kind of a hack, but you can use the lambda function:

mytest.testFunc = lambda *args, **kwargs: alternativeFunc(mytest, *args, **kwargs)

Solution 5

Since the original question asked for a way to call the function in the parent class and then do something extra also, I thought I'd point out that simply replacing the function can be problematic; if the parent class is modified in any way (the module it is part of is updated), then you may have to modify the code accordingly. Also, they may not want to recreate the original function just to add a bit to the end.

I definitely agree that creating a class inherited from testMod is the best option, I would just suggest calling the function from testMod and then modifying the result.

class testMOD(object):
    def testFunc(self, variable):
        var = variable
        return var + 12
        
class testMODNew(testMOD):
     def testFunc(self, variable):
         return testMOD.testFunc(self,variable) - 10.8
         
mytest4 = testMODNew()
print('Alternative4:', mytest4.testFunc(10)) # Alternative4: 11.2
         

One could make other changes, for example if you wanted objects with that class to keep track of how many times the method was called:

class testMODNew(testMOD):
     __testFuncCount__ = 0
     def testFunc(self, variable):
         self.__testFuncCount__ += 1
         return testMOD.testFunc(self,variable)
         
     def getTestFuncCount(self):
         return self.__testFuncCount__

mytest5 = testMODNew()
print('Original:',mytest5.testFunc(10)) #Original: 10
print('Original:',mytest5.testFunc(10)) #Original: 10
print('testFunc was called', mytest5.getTestFuncCount(), 'times.') 
#testFunc was called 2 times
Share:
25,313

Related videos on Youtube

Cody Chung
Author by

Cody Chung

Updated on July 09, 2022

Comments

  • Cody Chung
    Cody Chung almost 2 years

    I'm trying to replace a function defined within a class in order to modify its function (as in inner workings) without changing the actual code. I've never done this before, and, hence, am having some problems while replacing it.

    Changing the code will have me accessing the package within my Python library which is not much of an option.

    For example, if the module was called testMOD,

    class testMOD(object):
        def testFunc(self, variable):
            var = variable
            self.something = var + 12
    

    Then I would import testMOD, define a class (mytest = testMOD()), and access the defined function within the class, testFunc, and change it to already defined function.

    For instance,

    from somemodule import testMOD
    mytest = testMOD()
    
    def alternativeFunc(self, variable):
        var = variable
        self.something = var + 1.2
    
    # A problem here
    mytest.testFunc = alternativeFunc
    

    As you can see, if I just manually overwrite(?) the function in the class with my defined function it won't work properly.

    It doesn't give any syntax errors, however, the problem is that the replaced function thinks that the 'self' is another variable for the function and says that it requires another argument for the 'variable' variable (I guess that wasn't a good name).

    I want to make the replacing function to be exactly the same thing as the replaced function, but with additional code or some minor modifications. However, the 'self' is pretty much not working as it should be in a class.

    Would there be a way to properly implement a defined function to replace a function of an imported class?

  • Cody Chung
    Cody Chung almost 6 years
    Thanks for the answer! I was really confused when I thought what I did was exactly the same as you did, then I realized that I had to change the actual class, not the inherited class variable.
  • Edward Falk
    Edward Falk over 4 years
    Worth mentioning: it's not necessary to create a new instance of TestMOD. Monkey patching changes the class itself, which affects any existing instances as well. Calling test_original.testFunc(12) would also have had the same effect.
  • deed02392
    deed02392 over 4 years
    What should we do when the class is an internal class, i.e. objects are only instantiated internally to some external module? Now the only option I can see is to modify the source and maintain my own fork.
  • brian_ds
    brian_ds about 4 years
    Does method 3 only work in certain versions of Python? I tried it in 3.6.8 and it didn't update an existing instance like yours did.
  • mank
    mank over 2 years
    @EdwardFalk does monkey patching also change the class description. For example would the update show up with ??TestMod ?