What is monkey patching?

前端 未结 8 2041
[愿得一人]
[愿得一人] 2020-11-21 16:35

I am trying to understand, what is monkey patching or a monkey patch?

Is that something like methods/operators overloading or delegating?

Does it have anyt

8条回答
  •  北荒
    北荒 (楼主)
    2020-11-21 16:57

    What is a monkey patch?

    Simply put, monkey patching is making changes to a module or class while the program is running.

    Example in usage

    There's an example of monkey-patching in the Pandas documentation:

    import pandas as pd
    def just_foo_cols(self):
        """Get a list of column names containing the string 'foo'
    
        """
        return [x for x in self.columns if 'foo' in x]
    
    pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class
    df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
    df.just_foo_cols()
    del pd.DataFrame.just_foo_cols # you can also remove the new method
    

    To break this down, first we import our module:

    import pandas as pd
    

    Next we create a method definition, which exists unbound and free outside the scope of any class definitions (since the distinction is fairly meaningless between a function and an unbound method, Python 3 does away with the unbound method):

    def just_foo_cols(self):
        """Get a list of column names containing the string 'foo'
    
        """
        return [x for x in self.columns if 'foo' in x]
    

    Next we simply attach that method to the class we want to use it on:

    pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class
    

    And then we can use the method on an instance of the class, and delete the method when we're done:

    df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
    df.just_foo_cols()
    del pd.DataFrame.just_foo_cols # you can also remove the new method
    

    Caveat for name-mangling

    If you're using name-mangling (prefixing attributes with a double-underscore, which alters the name, and which I don't recommend) you'll have to name-mangle manually if you do this. Since I don't recommend name-mangling, I will not demonstrate it here.

    Testing Example

    How can we use this knowledge, for example, in testing?

    Say we need to simulate a data retrieval call to an outside data source that results in an error, because we want to ensure correct behavior in such a case. We can monkey patch the data structure to ensure this behavior. (So using a similar method name as suggested by Daniel Roseman:)

    import datasource
    
    def get_data(self):
        '''monkey patch datasource.Structure with this to simulate error'''
        raise datasource.DataRetrievalError
    
    datasource.Structure.get_data = get_data
    

    And when we test it for behavior that relies on this method raising an error, if correctly implemented, we'll get that behavior in the test results.

    Just doing the above will alter the Structure object for the life of the process, so you'll want to use setups and teardowns in your unittests to avoid doing that, e.g.:

    def setUp(self):
        # retain a pointer to the actual real method:
        self.real_get_data = datasource.Structure.get_data
        # monkey patch it:
        datasource.Structure.get_data = get_data
    
    def tearDown(self):
        # give the real method back to the Structure object:
        datasource.Structure.get_data = self.real_get_data
    

    (While the above is fine, it would probably be a better idea to use the mock library to patch the code. mock's patch decorator would be less error prone than doing the above, which would require more lines of code and thus more opportunities to introduce errors. I have yet to review the code in mock but I imagine it uses monkey-patching in a similar way.)

提交回复
热议问题