Skip to Content
Course content

198: Monkey Patching: Uses and Risks

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Wait, what does "monkey patching" actually mean in Python?

At its simplest, monkey patching is when you swap out a piece of code—usually a function or a method—at runtime. Because Python is so dynamic, classes and modules are just objects. That means you can reach into a module or a class and replace a function with your own version while the program is already running.

I like to think of it as "hot-swapping" a part in a machine without turning the machine off. You aren't changing the source code on the disk; you're changing how the code behaves in memory for that specific execution of your script.

When is this actually useful instead of just writing a wrapper?

In a perfect world, you'd just use inheritance or composition. But sometimes you're dealing with a third-party library that has a bug, or a method that's too slow, and you can't wait for the maintainer to merge your PR. Or, more commonly, you're writing tests and you need to stop a function from actually sending a real email or charging a credit card.

Here is a scenario I've run into often: mocking an external API call. Let's say you have a PaymentGateway class from a vendor that you can't modify. You don't want your unit tests to hit the real API.

import payment_vendor

class PaymentGateway:
    def charge(self, amount):
        # Imagine this makes a real network call to a bank
        print(f"Charging ${amount} to the real bank API...")
        return "TXN_12345"

# This is the monkey patch. 
# We define a fake version of the charge method.
def fake_charge(self, amount):
    print(f"Mocking charge of ${amount}. No money spent!")
    return "MOCK_TXN_999"

# Now we overwrite the original method with our fake one
PaymentGateway.charge = fake_charge

# Any code that uses PaymentGateway now uses our patch
gateway = PaymentGateway()
gateway.charge(100) # Output: Mocking charge of $100. No money spent!

By doing this, I've redirected the behavior of the entire class without having to change every single place in my codebase where PaymentGateway is instantiated.

What's the catch? Why is this considered a "code smell"?

Look, monkey patching is powerful, but it's a double-edged sword. The biggest issue is that it creates "magic" behavior. Imagine you're a new dev joining my team. You open the payment_vendor documentation, it says the charge method does X, but when you run the code, it does Y. You'll spend an hour hunting through the source code only to realize some obscure utility file in the /tests folder patched the method at runtime.

Here are the risks I've seen blow up in production:

  • Global Side Effects: If you patch a method in a module, that patch affects every other module that imports it. It's a global change.
  • Upgrade Nightmares: If you patch a library method and then update that library, the original method's signature might change. Your patch will still be there, but it might now be missing a required argument, causing your app to crash in a way that's incredibly hard to debug.
  • Testing Interference: If you forget to "un-patch" a method after a test, your subsequent tests are now running against mocked data, leading to false positives.

My rule of thumb: use it for testing (via libraries like unittest.mock which handle the cleanup for you), but avoid it in production unless it is the absolute last resort to fix a critical bug in a dependency.




📋 Practical Task

Patching a Flaky Third-Party Logger

You are using a third-party logging library called LegacyLogger. This library is designed to write logs directly to a file on the local disk, but for your current cloud environment, you need the logs to be stored in a list in memory so you can inspect them during a diagnostic run.

Your Task:

  1. Create a class LegacyLogger with a method log(self, message) that prints "Writing to disk: [message]".
  2. Create a separate function called memory_log(self, message) that appends the message to a global list called log_buffer.
  3. Monkey patch the LegacyLogger.log method using your memory_log function.
  4. Instantiate the logger, call the log method a few times, and print the log_buffer to prove the patch worked.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.