47

Say I have 2 different implementations of a class

class ParentA:
    def initialize(self):
        pass

    def some_event(self):
        pass

    def order(self, value):
        # handle order in some way for Parent A


class ParentB:
    def initialize(self):
        pass

    def some_event(self):
        pass

    def order(self, value):
        # handle order in another for Parent B

How can I dynamically let some 3rd class inherit from either ParentA or ParentB based on something like this?

class MyCode:
    def initialize(self):
        self.initial_value = 1

    def some_event(self):
        # handle event
        order(self.initial_value)


# let MyCode inherit from ParentA and run
run(my_code, ParentA)
shx2
  • 61,779
  • 13
  • 130
  • 153
Morten
  • 1,819
  • 5
  • 28
  • 37
  • I reopened the question becaused the dup was about changing the class of an instance after creation, and this question is about creating *classes* with different base classes. – shx2 Jul 05 '15 at 04:38

4 Answers4

92

Simply store the class-object in a variable (in the example below, it is named base), and use the variable in the base-class-spec of your class statement.

def get_my_code(base):

    class MyCode(base):
        def initialize(self):
          ...

    return MyCode

my_code = get_my_code(ParentA)
shx2
  • 61,779
  • 13
  • 130
  • 153
  • 4
    Smart and Simple. Thanks – ioaniatr Aug 06 '18 at 16:47
  • 6
    Depending on how often you call `get_my_code` with the same argument, this will create a lot of essentially identical but distinct classes. Some sort of memoization would be good. – chepner Aug 13 '20 at 14:01
  • 2
    FYI.... if anyone requires to have multiple parent classes (e.g. ParentA, ParentB both in above case), then you can use `base` to be a list of parent Classes, and then do `class MyCode(*base):`...... while instantiating, use: `my_code = get_my_code([ParentA, ParentB])` – Shailesh Appukuttan Aug 26 '20 at 16:32
  • Incorporating these comments: https://stackoverflow.com/a/66815839/5122790 – Chris Stenkamp Mar 26 '21 at 11:14
11

Also, you can use type builtin. As callable, it takes arguments: name, bases, dct (in its simplest form).

def initialize(self):
    self.initial_value = 1

def some_event(self):
    # handle event
    order(self.initial_value)

subclass_body_dict = {
    "initialize": initialize,
    "some_event": some_event
}

base_class = ParentA # or ParentB, as you wish

MyCode = type("MyCode", (base_class, ), subclass_body_dict)

This is more explicit than snx2 solution, but still - I like his way better.

PS. of course, you dont have to store base_class, nor subclass_body_dict, you can build those values in type() call like:

MyCode = type("MyCode", (ParentA, ), {
        "initialize": initialize,
        "some_event": some_event
    })
Filip Malczak
  • 3,124
  • 24
  • 44
  • Filip, when I use `type()` the parent will only have the methods as given in the 3rd argument. Any way to keep its methods and only override those given in the 3rd argument? – Morten Jan 12 '14 at 09:43
  • 1
    Ok I figured it out. Just add the child class to the tuple. – Morten Jan 12 '14 at 13:20
  • I'm stuck in a situation where apparently all of my bases are old style classes. ("a new-style class can't have only classic bases"). Or I'm in too deep with metaclasses. d'oh! – Mark Mar 23 '18 at 00:18
  • It sounds like a material for distinct question. Would you ask one on SO, link it here, please, I'll try to answer ;) – Filip Malczak Mar 24 '18 at 11:52
2

Just as a quick copy-and-paste-ready snippet, I've added the comments from shx2's answer to create this (memoized with a created_classes dict attribute, so that the classes created by successive identical calls with the same class will give identical classes):

class ParentA:
    val = "ParentA"

class ParentB:
    val = "ParentB"

class DynamicClassCreator():
    def __init__(self):
        self.created_classes = {}
    def __call__(self, *bases):
        rep = ",".join([i.__name__ for i in bases])
        if rep in self.created_classes:
            return self.created_classes[rep]
        class MyCode(*bases):
            pass
        self.created_classes[rep] = MyCode
        return MyCode

creator = DynamicClassCreator()

instance1 = creator(ParentA, ParentB)()
print(instance1.val) #prints "ParentA"

instance2 = creator(ParentB, ParentA)()
print(instance2.val) #prints "ParentB"

If you wanted to get fancy you could even make DynamicClassCreator a Singleton: https://stackoverflow.com/a/7346105/5122790

Louis Maddox
  • 5,226
  • 5
  • 36
  • 66
Chris Stenkamp
  • 337
  • 1
  • 2
  • 15
1

As an alternative to Chris's answer implementing the memoisation suggestion for shx2's answer, I'd prefer to use a memoize decorator (the end result is still a class but it's clearer to me that the function is the interface), and also use setdefault to simplify adding to the memo dict, and do not convert the names to string but use the tuple bases itself as the key, simplifying the code to:

class Memoize:
    def __init__(self, f):
        self.f = f
        self.memo = {}

    def __call__(self, *args):
        return self.memo.setdefault(args, self.f(*args))

class ParentA:
    def initialize(self):
        pass


class ParentB:
    def initialize(self):
        pass


@Memoize
def get_my_code(base):

    class MyCode(base):
        def initialize(self):
          pass

    return MyCode

a1 = get_my_code(ParentA)
a2 = get_my_code(ParentA)
b1 = get_my_code(ParentB)

print(a1 is a2) # True
print(a1 is b1) # False

(Not a good example as the code provided doesn't actually do anything other than overwrite the parent class's initialize method...)

Louis Maddox
  • 5,226
  • 5
  • 36
  • 66
  • 4
    StackOverflow is awesome. Just stumbled upon this very problem again just to find a Stackoverflow question that I answered myself, just to then find that somebody even improved upon my answer! Awesome! – Chris Stenkamp Oct 25 '21 at 14:47