monkey patching
programming
Python
software development
dynamic modification

What is monkey patching?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Monkey patching is a technique in dynamic languages where you modify or extend classes, modules, or functions at runtime without changing their source code. In Python, this means replacing methods, adding attributes, or overriding behavior on existing objects after they are defined. While monkey patching enables quick fixes, testing mocks, and third-party library extensions, it can make code harder to debug and maintain because the modifications are invisible at the source level.

How Monkey Patching Works

In Python, classes and modules are mutable objects. You can assign new functions to their attributes at any time:

python
1class Dog:
2    def speak(self):
3        return "Woof!"
4
5# Original behavior
6dog = Dog()
7print(dog.speak())  # Woof!
8
9# Monkey patch: replace the speak method
10def new_speak(self):
11    return "Bark! Bark!"
12
13Dog.speak = new_speak
14
15# All instances (existing and new) use the patched method
16print(dog.speak())  # Bark! Bark!
17print(Dog().speak())  # Bark! Bark!

The patch modifies the class itself, so all instances — even ones created before the patch — see the new behavior.

Adding New Methods

python
1class Calculator:
2    def add(self, a, b):
3        return a + b
4
5# Add a new method that didn't exist before
6def multiply(self, a, b):
7    return a * b
8
9Calculator.multiply = multiply
10
11calc = Calculator()
12print(calc.add(2, 3))       # 5
13print(calc.multiply(2, 3))  # 6

Patching Module-Level Functions

python
1import json
2
3# Save the original function
4original_dumps = json.dumps
5
6# Monkey patch json.dumps to add default indentation
7def pretty_dumps(obj, **kwargs):
8    kwargs.setdefault('indent', 2)
9    return original_dumps(obj, **kwargs)
10
11json.dumps = pretty_dumps
12
13print(json.dumps({"name": "Alice", "age": 30}))
14# {
15#   "name": "Alice",
16#   "age": 30
17# }

Patching Instance Methods

Patch a single instance without affecting the class or other instances:

python
1import types
2
3class Logger:
4    def log(self, message):
5        print(f"LOG: {message}")
6
7logger1 = Logger()
8logger2 = Logger()
9
10# Patch only logger1
11def debug_log(self, message):
12    print(f"DEBUG [{id(self)}]: {message}")
13
14logger1.log = types.MethodType(debug_log, logger1)
15
16logger1.log("test")   # DEBUG [140...]: test
17logger2.log("test")   # LOG: test (unaffected)

types.MethodType binds the function to the specific instance so self is passed correctly.

Use Case: Testing with unittest.mock

The most accepted use of monkey patching is in testing, where unittest.mock.patch temporarily replaces objects:

python
1import unittest
2from unittest.mock import patch
3
4def get_weather(city):
5    # In production, this calls an external API
6    import requests
7    response = requests.get(f"https://api.weather.com/{city}")
8    return response.json()
9
10class TestWeather(unittest.TestCase):
11    @patch('requests.get')
12    def test_get_weather(self, mock_get):
13        # Configure the mock
14        mock_get.return_value.json.return_value = {"temp": 72}
15
16        result = get_weather("NYC")
17
18        self.assertEqual(result["temp"], 72)
19        mock_get.assert_called_once_with("https://api.weather.com/NYC")

@patch monkey patches requests.get for the duration of the test and automatically restores it afterward.

Using patch as a Context Manager

python
1from unittest.mock import patch
2import datetime
3
4def get_current_year():
5    return datetime.date.today().year
6
7with patch('datetime.date') as mock_date:
8    mock_date.today.return_value.year = 2025
9    print(get_current_year())  # 2025
10# After the with block, datetime.date is restored

Use Case: Extending Third-Party Libraries

python
1import pandas as pd
2
3# Add a custom method to all DataFrames
4def describe_nulls(self):
5    null_counts = self.isnull().sum()
6    return null_counts[null_counts > 0]
7
8pd.DataFrame.describe_nulls = describe_nulls
9
10df = pd.DataFrame({"a": [1, None, 3], "b": [4, 5, None]})
11print(df.describe_nulls())
12# a    1
13# b    1

Monkey Patching in Other Languages

Ruby

ruby
1class String
2  def shout
3    self.upcase + "!!!"
4  end
5end
6
7puts "hello".shout  # HELLO!!!

JavaScript

javascript
1// Patching a prototype (generally discouraged)
2Array.prototype.last = function() {
3    return this[this.length - 1];
4};
5
6console.log([1, 2, 3].last()); // 3

Risks and Drawbacks

python
1# Problem 1: Breaks expectations
2# Other code assumes json.dumps has default behavior
3json.dumps = pretty_dumps  # Now ALL code in the process gets indented output
4
5# Problem 2: Hard to debug
6# Stack traces show the patched function, not where the patch was applied
7
8# Problem 3: Upgrade breakage
9# If a library adds a method with the same name as your patch, conflicts occur
10
11# Problem 4: Order-dependent behavior
12# If module A patches X, and module B also patches X, the last one wins

Common Pitfalls

  • Patching the wrong import path: In Python, patch('module_a.requests.get') must match where requests is imported, not where it is defined. If module_a does from requests import get, patch module_a.get, not requests.get.
  • Forgetting to restore the original: Without unittest.mock.patch or a try/finally block, a monkey patch persists for the entire process lifetime. Always restore the original function after patching, especially in tests.
  • Patching built-in types: Patching built-in types like str, int, or list is not allowed in CPython (they are implemented in C). Use wrapper classes or functions instead.
  • Breaking other tests with global patches: A monkey patch applied in one test leaks into subsequent tests if not cleaned up. Use unittest.mock.patch as a decorator or context manager to ensure automatic cleanup.
  • Making code untestable by relying on monkey patches in production: If your production code depends on monkey patches to function, it becomes fragile and hard to reason about. Use proper dependency injection, subclassing, or composition instead.

Summary

  • Monkey patching modifies classes, modules, or functions at runtime without changing source code
  • In Python, assign new functions to class attributes: MyClass.method = new_function
  • The primary legitimate use is in testing with unittest.mock.patch, which patches temporarily
  • Avoid monkey patching in production code — prefer dependency injection, subclassing, or adapter patterns
  • Always restore original behavior after patching, either manually or with context managers

Course illustration
Course illustration

All Rights Reserved.