1

I'm currently working with a 3Rd party app. This 3rd party app has thoses class defined :

 class VeryBaseClass(object):
      def __init__():
          pass

 class BaseClass(VeryBaseClass):
     def specific_method():
          pass

And then, looots of theses:

 class Componenent[1,2,3,4....129883](BaseClass):
     def other_specific_method():
         pass

I can't modify any of these classes . So , for me to override/supplement methods here, I just have to create a class that inherits from Component, where I can change the methods effortlessly.

The problem is, making MyComponent1, then MyComponent2, MyComponent3, etc... all for copy-pasting the exact same code in the content of the class and just changing the inheritance is very annoying, and not DRY at all!

So, is there a way to create, for example , this class:

class MyComponentGeneric(Component1, Component2, Component3....):
      pass

Where MyComponentGeneric would not inherit from EVERY class listed, but could inherit from one OR another, depending of my declaration of the instance ?

Thanks!

Edit with more concrete code :

Actually, I've tried things that belonged in every answer, but I always end up facing the same error :

I made a Factory as Chepney advised :

# This method is the one I use as a replacement
def newrender(self, name, value, attrs=None):
    result = super().render(name, value, attrs)
    if self.image is None:
        return result
    else:
        return '<img class="form-image" height="100" width="100" src="{}"/>'.format(self.image.url) + result


def MyWidgetFactory(widget, picture):
     newclass = type("WithImage" + widget.__name__, (widget,), dict(render=newrender))
     newclass.image = image
     return newclass()

But as soon as my newrender method launches, I get this error :

result = super().render(name, value, attrs) RuntimeError: super(): __class__ cell not found

Is it because of a bad usage of the factory or something else ?

5 minutes later edit Okay, I just had to populate the super cell by calling it with super(type(self), self). Not quite sure how it works but, heh, it worked!

Thanks everyone !

Hurlu'
  • 330
  • 4
  • 13
  • *Might* be a use case for a meta-class – brianpck Nov 02 '16 at 14:13
  • To customize creation of classes, usually [metaclasses](http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python) are used. Exact application may vary depending on actual requirements. – Łukasz Rogalski Nov 02 '16 at 14:14

3 Answers3

4

This is a use case for a dictionary (or possibly a list, if the dictionary keys are just a sequence of consecutive integers):

base_classes = {
    1: Component1,
    2: Component2,
    # ...
}

def ComponentFactory(comp_type):
    return base_classes(comp_type)()

Here, ComponentFactory creates the instance for you, rather than creating a new class that essentially would just wrap an existing class without actually adding to it.


If you really need a new type, you can create that in the factory as well:

def ComponentFactory(comp_type):
    new_type = type('SomeTypeName', (base_classes[comp_type],), {})
    return new_type()

However, you should probably take care that you only create one such wrapper per real class, so that you don't end up with a bunch of identical singleton classes.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • I understand the use of the factory, however, if I understand well, I could resort to monkey-patching inside the factory to get the desired modified class, right? Monkey-patching doesn't seem to work well in my framework though, even working with instances of classes instead of whole classes :/ – Hurlu' Nov 02 '16 at 14:58
  • 1
    I would avoid monkey-patching when possible. It breaks encapsulation, and assumes a stability for the thing you are patching which may not exist. – chepner Nov 02 '16 at 15:01
1

it is difficult to answer without a close look at the codebase. But, to me it looks like a problem you can solve with monkey patching.

In other words you could add method to Component1, Component2, Component3 or even substitute existing methods of those classes.

Remeamber python is for sure OOP, but it also provides a set of "dynamic" tools (such as monkey pathching) you can use fruitfully in many scenarios.

Cheers

Chralie

Charlie
  • 1,750
  • 15
  • 20
  • Already tried my hand at monkey-patching it, but I get errors about missing arguments, so I kinda abandoned this path. I may try my hand again at it if the other solutions aren't satisfactory though! – Hurlu' Nov 02 '16 at 14:39
  • Note that you can mokey patch either a class and/or an object (aka an instance of a class). Just have a look here https://filippo.io/instance-monkey-patching-in-python/. This article was very useful for me! – Charlie Nov 02 '16 at 14:46
1

One, you wouldn't name your classes MyComponent2112, you'd have a dictionary with classes as values. Easier to work with.

Then just create the classes in a loop:

import third_party_module

mycomponents = dict()

for componentname in dir(third_party_module):
    if not componentname.startswith('Component'): continue
    baseclass = getattr(third_party_module, componentname)
    if not issubclass(baseclass, third_party_module.VeryBaseClass): continue
    number = int(componentname[9:])

    class MyComponent(baseclass):
        # Your updates here
    mycomponents[number] = MyComponent
RemcoGerlich
  • 30,470
  • 6
  • 61
  • 79
  • Thanks! I think I misled you a bit by making the number of classes moe important than what it was, but I appreciate your solution. However, I'd like my newliny created classes to be named MyClassDoesThis, MyClassDoesThat, instead of putting them in a dictionary. Any hint to generate a dynamic class name? Tried to modify \_\_name__, but it still could not call the class by it's new name afterwards :/ – Hurlu' Nov 02 '16 at 14:57
  • I don't like dynamic variable names, usually you then need strange code to use them dynamically as well, there's a famous blog post I want to link to but I can't recall right now. – RemcoGerlich Nov 02 '16 at 15:10
  • 1
    Anyway you _can_ set a global variable with `globals()['yourclassname'] = MyComponent` – RemcoGerlich Nov 02 '16 at 15:10
  • If you want the class to actually have that name as well (instead of all of them having the name "MyComponent" internally) then you need to use the three-argument form of `type` as in chepner's answer. – RemcoGerlich Nov 02 '16 at 15:11