Monkeying Around with Python: A Guide to Monkey Patching
Tags: python, webdev, programming, testing
Published: 2024-02-19 16:32:48


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e9u4au6avxccew81jmzv.png)


### What is Monkey Patching in Python?
Imagine modifying a car's engine while it's running. Monkey patching works similarly, allowing you to **dynamically alter or extend a class or module's behavior at runtime.** It involves changing or adding methods or attributes to existing modules or classes.

### Extending a Class
```py
class Cat:
  def meow(self):
    return "Meowww!"

# Monkey patch to extend functionality
def ragdoll_meow(self):
  return "Miaowww!"

original_meow = Cat.meow
Cat.meow = ragdoll_meow

ragdoll = Cat()
print(ragdoll.meow())  # Output: Miaowww!

```
Here, we replaced the `meow()` method with a breed-specific one for Ragdolls. Remember, this only affects instances created `after` patching.

### When to use Monkey Patching?

While alternatives like **Subclassing, Decorators, Dependency Injection, and Wrapper Classes** offer structured ways to modify or extend functionality, there are situations where monkey patching becomes a preferred choice:

- **Fixing third-party bugs**: Patch the problematic function with your fix without modifying the original library.
- **Testing and mocking**: Isolate specific components by patching their behavior, creating controlled testing environments.
- **Experimentation**: Try out different functionalities without permanent code changes.
- **Debugging**: Temporarily replace problematic code to pinpoint the issue's source.


### Real World Use Cases

**1. Fixing a Library Bug**
Imagine a library function you rely on has a minor bug. Instead of waiting for a fix, you can monkey patch it with your temporary solution:

```py
# Original buggy function
def calculate_area(length, width):
  return length * width  # Missing multiplication by 2

# Monkey patch the bug
def fixed_calculate_area(length, width):
  return length * width * 2

original_area = calculate_area
calculate_area = fixed_calculate_area

print(calculate_area(5, 3))  # Output: 30 (correctly calculated)
```

**2. Mocking for Testing**
Mocking external dependencies during testing helps isolate your code and ensures its functionality even without external systems running. Here's how you might mock a network call:

```py
# Original function fetching data
def get_data_from_api():
  # Makes an actual API call

# Monkey patch to return mock data
def mock_get_data_from_api():
  return {"data": "mocked"}

original_data_func = get_data_from_api
get_data_from_api = mock_get_data_from_api
```

**2.1 Using Python's `patch()`**
The `unittest.mock` module has `patch()` that allows you to temporarily replace a target with a mock object.

```py
from unittest.mock import patch

@patch("module.function")
def test_function(mock_function):
    # Your test logic here
```
Read more about it <a href="https://docs.python.org/3/library/unittest.mock.html">here</a>.

**3. Logging**

```py
import logging

# Define custom log methods
def log_info(self, message):
    self.log(logging.INFO, message)

def log_fail(self, message):
    self.log(logging.FAIL, message)

# Monkey patching the Logger class
logging.Logger.info = log_info
logging.Logger.fail = log_fail

logger = logging.getLogger("app_logger")
logger.info("This is an INFO message")
logger.fail("This is a FAIL message")
```

### Problems to Watch Out For
While monkey patching is powerful, it comes with some problems. 
- **Debugging complexity**: Altered code can make debugging trickier, as the original behavior is hidden.
- **Unintended consequences**: Changes might affect unforeseen parts of your application.
- **Version control challenges**: Tracking and managing monkey-patched code separately becomes necessary.

### Conclusion
Monkey patching offers flexibility at runtime in Python development. Remember, even monkeys need bananas in moderation. Patch wisely only if required. Keep your code sane! :monkey: