Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to overwrite a imported python class for all calls

Tags:

python

class

I create a python-packages /MyLibPackage which I will import in my projects.

MyLibPackage.____init____.py includes mymodiciation.py. Furthermore the MyLibPackage Folder contains another file :base_classes.py(=external project)

mymodiciation.py imports "from base_classes import *".

Goal: I can import MyLibPackage which has all classes from base_classes (=external project). And if I have need to modifiy some classes or functions, I can overwrite this in mymodiciation.py. It works but I got a problem. For example:

I overwrite this classes in mymodiciation.py:

class Bookcollection(Bookcollection):
   new_member = "lalala"


class user(user):
   def get_books(self):
      return Bookcollection()

if I do:

from MyLibPackage import *
x = user()
books = x.get_books()

then the object Bookcollection has the property "new_member". Good! But if i will do this:

from MyLibPackage import *
x = shelf() #this class is not overwritten and used also the object "Bookcolelction"
books = x.get_books()

then the object Bookcollection has NOT the property "new_member" because he is instanced with MyLibPackage.base_classes.Bookcollection and not with my overwritten class MyLibPackage.mymodiciation.Bookcollection

How can I say: If I overwrite a class in mymodiciation then MyLibPackage has to use this although when the call cames from MyLibPackage.base_classes.shelf (get_books).

like image 385
bob morane Avatar asked Apr 05 '12 10:04

bob morane


People also ask

How do you overwrite a class in Python?

In Python method overriding occurs by simply defining in the child class a method with the same name of a method in the parent class. When you define a method in the object you make this latter able to satisfy that method call, so the implementations of its ancestors do not come in play.

What happens when you import a module multiple times Python?

So each module is imported only one time. To better understand import mechanics I would suggest to create toy example. So import really happens only once. You can adjust this toy example to check cases that are interesting to you.

Does import * import everything Python?

Polluting the namespace, import * imports all the functions and classes in your own namespace that may clash with the function and classes you define or function and classes of other libraries that you may import.


2 Answers

What you want to do is called "monkey patching", and has little to do with Object Orientation.

Python does support it, but you have control over all your classes, you should seriously review your project to check if you will really need it.

Maybe using a framework like Zope Component Architecture, which allows you to mark classes with interfaces, and provides adapter objects so that you can cleanly use one object as having some interface it was not designed to have in first place, will be a better idea.

That said, what you are asking for is to change the class, in the other module, where it is - so that the changes are visible to all the other modules.

You do just that: change the class in the module where it belongs. In Python it can be done simply attributing your new class to the desired name, in the module of origin:

import base_classes

class Bookcollection(base_classes.Bookcollection):
   new_member = "lalala"

base_classes.Bookcollection = Bookcollection

(For things like this to work, you have to avoid "from x import *" in any project larger than single script - in this case you had 2 variables with the same name, and different meanings throughout your code:the base class, and the inherited class, for example. Python namespaces allow you to avoid that).

So, this will change the Bookcollection class in the base_class module - BUT only for code that will reference it from this point and on on your execution chain. If the "x" class in your example,is defined in the "base_classes" module, or otherwise is defined before the "MyModule" is imported, it will get a reference to the old "Bookcollection" class.

As you can see, it can quickly become a mess, and if you really choose this approach, the only way of keeping your project even usable, is to have unit tests to verify that all the classes that you want patched, are actually patched. Even the importing order of modules will make a difference, as you see. If you have tests place, they will break if you make imports in an order that breaks your monkey patching.

If you just need to add and replace things in an existing class, you can monkey patch the class itself to replace its components, instead of monkey patching the module it is in to replace the class. This way, the import order of modules won't matter much -- it will affect even existing instances of that class:

 import base_classes

 base_classes.Bookcollection.new_member = "lalala"

 def new_bookcol_method(self):
      pass

 # to replace or register a method in the other class:
 base_classes.Bookcollection.__dict__["old_bookcol_method"] = new_bookcol_method

This will give you a more consistent behaviour than trying to assign a new class (which is an object in itself) to the same name in the original module.

All in all, you should either do as @jamesj suggests in his answer, and use distinct classes, or if you need the dynamic behaviour, use a maintainable framework for that, like Zope Component Architecture. And whatever approach you take, do write unit tests.

like image 125
jsbueno Avatar answered Oct 05 '22 22:10

jsbueno


Having classes with the same name is generally a bad idea as you get namespace clashes. I would recommend changing your MyLibPackage.base_classes.Bookcollection to MyLibPackage.base_classes.BaseBookcollection or similar. This should then work as expected.

like image 38
Jim Jeffries Avatar answered Oct 05 '22 23:10

Jim Jeffries