Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
198: Monkey Patching: Uses and Risks
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:
- Create a class
LegacyLoggerwith a methodlog(self, message)that prints"Writing to disk: [message]". - Create a separate function called
memory_log(self, message)that appends the message to a global list calledlog_buffer. - Monkey patch the
LegacyLogger.logmethod using yourmemory_logfunction. - Instantiate the logger, call the
logmethod a few times, and print thelog_bufferto prove the patch worked.
There are no comments for now.