Search code examples
pythonclassmodulepatchmonkey

Monkey patching a class in another module in Python with other imports


I am trying to monkey patch a python module to override a method within the class. This is a proxy module for simplification and not the actual code or module I am overwriting but follows the same structure.

The module looks like internally:

dog_module.py

from ._util    import b
from ._util    import log


class Bork(BorkBase):

    def __init__(self, verbose=False, options=None):
        self.a = 42

    def define_bork(self, bark):
        result = bark
        log.info('Importing: %r', bark)
        return result

where _util.py lives within the same parent folder as this dog_module.py

I modified the package code at the source (within the python/.../site-packages/) and this works fine, but I want to monkey patch it so it's easier for others to deploy (so they don't have to go into the source code and make these changes themselves on their machines).

I have tried overriding def define_bork(): in my script with:

import dog_module

def new_bork(self, bark, breed):
        result = bark
        log.info('Importing: %r', bark)
        if breed == "big":
          result = bark + "!"
        return result

class Dog:
    def __init__(self, color, breed):
        self.color = color
        self.breed = breed
        self.woof = self.woof()
        
    def woof(self):
        dog_module.Bork.define_bork = new_bork
        bark = dog_module.Bork()
        return bark.define_bork("woof",self.breed) # returns string "woof!"


my_dog_spot = Dog(“black”, “big”)

but the problem is that it seems to properly modify the "define_bork()" method within the original dog_module, but it throws:

     61 def new_bork(self, bark, breed):
     62           result = bark
---> 63           log.info('Importing: %r', bark)
     64           if breed == "big":
     65               result = bark + "!"
     66           return result

NameError: name 'log' is not defined

There are other methods within _utils.py other than just log, so removing it entirely is not an option. I figured it would still correctly inherit everything from the class and just override the one method, but it seems to not work that way.


Solution

  • A function will see the global variables of the module it is defined in. And by "global variables", we mean also any other functions, methods constants present in that module, including ones that were imported from 3rd modules.

    In this case, you need the function you are defining in the overriding script to have access to some names of the original module, were it will replace another function -

    All you need to do, if you can import the other module as you are doing here, is to refer to the qualified name of those names you need - so, instead of log you refer to dog_module.log inside your function that needs access to these values.

    Your code will work with this simple replacement:

    import dog_module
    
    def new_bork(self, bark, breed):
            result = bark
            dog_module.log.info('Importing: %r', bark)
            if breed == "big":
              result = bark + "!"
            return result
    
    ...