mirek
mirek

Reputation: 1360

dynamic parent class in python

The django-file-resubmit module (file widgets.py) makes this import:

from django.forms import ClearableFileInput

and then it defines class based on ClearableFileInput:

class ResubmitBaseWidget(ClearableFileInput):
    # ...some code

I try to use the module with different base class and it works well. But I have to patch the import command in module code. (DBAdminClearableFileInput is inherited from django.forms.ClearableFileInput in an other 3rd party module):

from db_file_storage.form_widgets import DBAdminClearableFileInput as ClearableFileInput

My question is: Could the code of django-file-resubmit module be rewritten more clever, so it could be used with DBAdminClearableFileInput as parameter?

Note: I'm not sure if this is not duplicate question. However I think here are special problems of modules design and a question if some Pull Request could be made or what is the best approach how to use both modules without changing them.

Upvotes: 0

Views: 68

Answers (1)

Tom Dalton
Tom Dalton

Reputation: 6190

It sounds like what you might really want is co-operative multiple inheritance. E.g. You want to have

class MyFileInput(DBAdminClearableFileInput, ResubmitBaseWidget):
    pass

For that to work, both DBAdminClearableFileInput and ResubmitBaseWidget would need to be written with co-operative multiple inheritance in mind. It may not even be (theoretically) possible depending on how the end-state behaviour has to look. E.g. if DBAdminClearableFileInput wants to render the widget as <foo> and ResubmitBaseWidget wants to render the widget as <bar>, one of them has to 'win' (in the absence of additional code you might write yourself in MyFileInput.

It's possible (though probably unlikely) that multiple inheritance will 'just work', depending on what methods etc both those classes override and whether they make they already make the correct calls to super().

It's probably worth a try at least, in the worst case scenario you can add some 'glue' to your MyFileInput class to make it work.

Here's a trite example

class Elephant:  # Represents ClearableFileInput
    def colour(self):
        return "Grey"

class BrownElephant(Elephant):  # Represents ResubmitBaseWidget
    def colour(self):
        return "Brown"

class PinkElephant(Elephant):  # Represents DBAdminClearableFileInput
    def colour(self):
        return "Pink"

Now, at the moment, these classes do not cooperate, and so if you do multiple inheritance, you get:

class BrownAndPinkElephant(BrownElephant, PinkElephant):
    pass

nelly = BrownAndPinkElephant()
print(nelly.colour())

Will print "Brown", since the Method Resolution Order starts with BrownElephant, which returns "Brown" without ever calling super().colour(), and so Pink and 'default' Elephant's methods are never even called.

You could 'fix' this in a very limited (but might be enough for your purposes) with a hacky 'glue' method like this:

class BrownAndPinkElephant(BrownElephant, PinkElephant):
    def colour(self):
        colours = [
            BrownElephant.colour(self),
            PinkElephant.colour(self),
        ]
        return " and ".join(colours)

nelly = BrownAndPinkElephant()
print(nelly.colour())

Now the printed output is "Brown and Pink", which is more sensible (at least within the context of this example!). Hopefully you can see that you attempt to implement similar things for a subclass of DBAdminClearableFileInput, ResubmitBaseWidget to give you control over what aspects of each class end up being used in the final class.

It's worth saying, there are an awful lot of pitfalls with this approach and I wouldn't recommend it for anything 'serious'. However, when you have 2 classes with a common base class, that you want to combine, and you don't control the source code of either, then this may be a possible solution.

Upvotes: 1

Related Questions