user32882
user32882

Reputation: 5877

Is there a way to monkey patch a class inside a module before the module body is executed in python?

I have file main.py which contains the following code:

class A:
  def __init__(self, a):
    self.a = a
  def run(self):
    return self.a+10

a = A(4)
print(a.run())

In file test.py, I tried to monkey patch class A in main.py as follows:

import main

class A:
  def __init__(self, a):
    self.a = a
  def run(self):
    return self.a+5 

main.A = A

Unfortunately, when I run import test from a python interpreter, the module still prints out 14 as opposed to my expected output which is 9.

Is there a way to monkey patch a class inside a module before the module body is executed?

Upvotes: 0

Views: 356

Answers (1)

Lenormju
Lenormju

Reputation: 4368

The problem here is that when you imported the main.py file, it executed the code a = A(4) using the real implementation of the class A. Then the rest of your test.py was executed and you replaced the A reference, but it was too late. You can check that by adding in your test :

print(__name__)                     # __main__
print(main.__name__)                # so70731368_main
print(A.__module__)                 # __main__
print(main.A.__module__)            # __main__
print(main.a.__class__.__module__)  # so70731368_main

Here, __main__ is a bit confusing but that's how Python call the first file you run (in your case test.py). The a instance is declared in the so70731368_main module, and it used the A class from the same module, you just changed A after the fact with the definition from the test file (__main__).

The fact that you need to patch two definitions (A and a) defined in the same file is very tricky. unittest.mock.patch is not powerful enough to patch inside an import (it patches after the import).
You can not, in a clean and simple way, prevent a to be instantiated as a main.A (real) class and get printed. What you can do is patch it after, for later uses, that is what you showed.

To answer directly your question : "patching" means replacing one reference by another, so the reference has to already be defined. In your example, it would require to patch between the class definition and the class instantiation (for the print to not use the real a), which is not supported.
There is no simple solution to this problem. If you have control over the code of the main.py file, then try to change it so that it does not instantiate a at import time.

Upvotes: 1

Related Questions