Dependency Injection & Monkey Patching in Python
Two techniques constantly come up when you want flexible, testable Python code: dependency injection and monkey patching. They solve a similar problem — swapping out a piece of behaviour — but from opposite ends. Dependency injection designs for change up front; monkey patching forces change from the outside, at runtime. Let's look at both.
1. Dependency Injection
Dependency Injection (DI) is a simple idea: instead of a class creating the things it depends on, you pass those dependencies in. This gives you loose coupling, and combined with Python's duck typing it makes code easy to extend and test. What feels like heavy machinery in Java or C# is often just a function argument in Python.
Here's a small example — injecting the authenticate/authorize behaviour:
class Command:
def __init__(self, authenticate=None, authorize=None):
self.authenticate = authenticate or default_authenticate
self.authorize = authorize or default_authorize
def execute(self, user, action):
self.authenticate(user)
self.authorize(user, action)
return action()
if in_sudo_mode:
command = Command(always_authenticated, always_authorized)
else:
command = Command(config.authenticate, config.authorize)
command.execute(current_user, delete_user_action)
The Command class doesn't care how authentication works —
it just calls what it was given. You can swap in a real authenticator in
production and a fake one in tests, without changing the class at all.
You don't even need constructor injection; you can set the dependencies as attributes after the fact:
command = Command()
if in_sudo_mode:
command.authenticate = always_authenticated
command.authorize = always_authorized
else:
command.authenticate = config.authenticate
command.authorize = config.authorize
command.execute(current_user, delete_user_action)
The big win: flexible, trivial unit testing. If a class is reused across applications or needs to vary its behaviour at runtime, DI is a great default.
2. Monkey Patching
Monkey patching means replacing an attribute, method, or function on an existing object, class, or module at runtime — from the outside, without editing its source. Because everything in Python is a mutable object (even classes and modules), you can just reassign an attribute.
Say a class talks to a payment gateway you don't want to hit during tests:
# payments.py
class PaymentGateway:
def charge(self, amount):
# real network call to a payment provider
return real_provider.charge(amount)
# in your test — replace the method at runtime
def fake_charge(self, amount):
return {"status": "ok", "amount": amount}
PaymentGateway.charge = fake_charge # monkey patch!
gateway = PaymentGateway()
print(gateway.charge(100)) # {'status': 'ok', 'amount': 100}
Nothing inside PaymentGateway changed — we swapped its
charge method from the outside. This is exactly what testing
tools do under the hood; in practice you'd use the standard library instead
of patching by hand, so it gets cleaned up automatically:
from unittest.mock import patch
with patch.object(PaymentGateway, "charge", return_value={"status": "ok"}):
gateway = PaymentGateway()
assert gateway.charge(100)["status"] == "ok"
# original method is automatically restored here
Monkey patching is powerful but use it sparingly. It changes behaviour invisibly, can surprise the next reader, and if you forget to restore the original (outside a context manager) it leaks into unrelated code. Great for tests and quick hotfixes to third-party code — risky as an everyday design tool.
DI vs. Monkey Patching — which one?
Prefer Dependency Injection when you own the code and can design for swappability — it's explicit and self-documenting. Reach for Monkey Patching when you can't change the code (a third-party library) or need to override behaviour in a test that wasn't built for injection. DI is a design choice; monkey patching is an escape hatch.