Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: how to monkey patch (swap) classes

Tags:

python

Lets say I have the following 2 classes in module a

class Real(object):
    ...
    def print_stuff(self):
        print 'real'

class Fake(Real):
    def print_stff(self):
        print 'fake'

in module b it uses the Real class

from a import Real
Real().print_stuff()

How do I monkey patch so that when b imports Real it's actually swapped with Fake?

I was trying to do like this in initialize script but it doesn't work.

if env == 'dev':
    from a import Real, Fake
    Real = Fake 

My purpose is to use the Fake class in development mode.

like image 900
James Lin Avatar asked Sep 28 '15 03:09

James Lin


2 Answers

You can use patch from the mock module. Here is an example:

with patch('yourpackage.b.Real') as fake_real:
    fake_real.return_value = Fake()
    foo = b.someClass()
    foo.somemethod()
like image 106
jordanm Avatar answered Sep 30 '22 17:09

jordanm


The issue is that when you do -

from a import Real, Fake

You are basically importing those two classes into your initialize script's namespace and creating Real and Fake names in the initialize script's namespace. Then you make the name Real in initialize script point to Fake , but that does not change anything in the actual a module.

If initialize script is another .py module/script at runs at the start of your original program , then you can use the below -

if env == 'dev':
    import a
    a.Real = a.Fake

Please note, this would make a.Real to refer to the Fake class whenever you use Real from a module after the above line is executed.


Though I would suggest that a better way would be to do this in your a module itself, by making it possible to check the env in that module, as -

if <someothermodule>.env == 'dev':
    Real = Fake

As was asked in the comments -

Doesn't import a also import into initialize script's namespace? What's the difference between importing modules and classes?

The thing is that when you import just the class using from a import class , what you actually do is create that variable, class in your module namespace (in the module that you import it to) , changing that variable to point to something new in that module namespace does not affect the original class in its original module-object, its only affected in the module in which its changed.

But when you do import a, you are just importing the module a (and while importing the module object also gets cached in the sys.modules dictionary, so any other imports to a from any other modules would get this cached version from sys.modules ) (Another note, is that from a import something also internally imports a and caches it in sys.modules, but lets not get into those details as I think that is not necessary here).

And then when you do a.Real = <something> , you are changing the Real attribute of a module object, which points to the class, to something else, this mutates the a module directly, hence the change is also reflected, when the module a gets imported from some other module.

like image 26
Anand S Kumar Avatar answered Sep 30 '22 17:09

Anand S Kumar