Monkey Patching in Python

Monkey Patching in Python

Β·

3 min read

Monkey patching refers to modifying or updating a piece of code or class or any module at runtime. With that, we can change the behaviour or working of a class/ module dynamically without affecting the original source code.

This can be useful in a variety of situations, such as:

  • Debugging and testing

  • Extending third-party libraries

  • Hotfixes

  • Backward compatibility

Among these, the most common use case is extending third-party libraries. Let's explore it through an example: Let's say there is a third-party library called logy that defines a class: WithLog which provides logs before and after executing a passed-in function. Here is the implementation: πŸ‘‡

##------- logy library ------##
from datetime import datetime

def get_time():
    return datetime.now().strftime('%d/%m/%Y %H:%M:%S')

class WithLog:
    def __init__(self, func):
        self._func = func

    @property
    def time(self):
        return get_time()

    def __call__(self,  *args, **kwargs):
        print(f'{self.time}: {self._func.__name__}() -> Started')
        self._func(*args, **kwargs)
        print(f'{self.time}: {self._func.__name__}() -> Completed')

And we are going to use it in our script to get logs for our 'sum' function, like: πŸ‘‡

##------- our script ------##
import logy

def sum(a, b):
    return a + b

result = logy.WithLog(sum)(2, 3)
# 05/02/2023 05:47:50: sum() -> Started
# 05/02/2023 05:47:50: sum() -> Completed

print(result) # None

While running our script, we see that: although the library is giving logs for sum(), it is swallowing the return value; But we wanted to show that result in the output. How can we get around this issue πŸ€”? Yes, you guessed it right - Monkey Patching! 🐡

We will create a new call method that returns the result of the input function and we will monkey-patch the __call__ method of WithLog with the call function that we created. If you are feeling confused about how this is going to work out, then I strongly suggest that you go through Python Scope before reading ahead.

##------- our script ------##
import logy

def monkey_call(self, *args, **kwargs):
    print(f'{self.time}: {self._func.__name__}() -> Started')
    result = self._func(*args, **kwargs)
    print(f'{self.time}: {self._func.__name__}() -> Completed')
    return result

logy.WithLog.__call__ = monkey_call

# ... rest of the code

print(result) # 8

Nice! we have successfully monkey-patched WithLog to return the result of the input function. Now, what if we wanted the logs with just the time and not the date 🧐? Yes, we will monkey-path the utility function: get_time of logy. πŸ˜›

##------- our script ------##
import logy
from datetime import datetime

def monkey_get_time():
    return datetime.now().strftime('%H:%M:%S')

logy.get_time = monkey_get_time

# ... rest of the code

Monkey patching is not confined to just third-party libraries, you can monkey-patch standard libraries as well. Go on, try monkey patching few standard libraries that you know.

⚠️ NOTE: Although it is useful in certain scenarios, monkey patching is sometimes considered a bad practice and hence should be used carefully. It creates discrepancies between the original source code and observed behaviour, as the definition of the object does not accurately describe how the object is behaving in the code. This makes the code harder to understand and maintain and can create confusion among developers.


Well, that’s all for this article. Thanks for reading! πŸ™

I hope you liked this article and if you have any questions or suggestions, please drop them in the comments. Happy Coding.. πŸ‘‹

Β