Динамическое наследование в Python

Скажем, у меня есть 2 разные реализации класса

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

Как я могу динамически позволить некоторым 3-м классам наследовать ParentA или же ParentB на основе чего-то вроде этого?

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)

4 ответа

Решение

Просто сохраните объект класса в переменной (в приведенном ниже примере он называется base), и используйте переменную в спецификации базового класса вашего class заявление.

def get_my_code(base):

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

    return MyCode

my_code = get_my_code(ParentA)

Также вы можете использовать type встроенный. Как вызываемый, он принимает аргументы: name, bases, dct (в простейшем виде).

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)

Это более явно, чем решение snx2, но все же - мне нравится его путь лучше.

PS. конечно, вам не нужно хранить ни base_class, ни subclass_body_dict, вы можете построить эти значения в type() позвоните как:

MyCode = type("MyCode", (ParentA, ), {
        "initialize": initialize,
        "some_event": some_event
    })

В качестве альтернативы ответу Криса, реализующему предложение memoization для ответа shx2 , я бы предпочел использовать декоратор memoize (конечным результатом по-прежнему является класс, но мне яснее, что функция представляет собой интерфейс), а также использовать setdefaultчтобы упростить добавление в памятку и не преобразовывать имена в строку, а использовать кортеж basesсебя в качестве ключа, упрощая код до:

      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

(Не очень хороший пример, так как предоставленный код на самом деле не делает ничего, кроме перезаписи родительского класса. initializeметод...)

Так же, как фрагмент, готовый для быстрого копирования и вставки, я добавил комментарии из ответа shx2, чтобы создать это:

      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"

Если вы хотите пофантазировать, вы даже можете сделать DynamicClassCreator синглтоном: /questions/3564781/est-li-prostoj-elegantnyij-sposob-opredeleniya-singletonov/3564782#3564782

Другие вопросы по тегам