Избегать вызова __init__ класса Mixin несколько раз - PullRequest
1 голос
/ 17 июня 2019

Я работаю над большой структурой, которая работает с Mixin для регистрации дескрипторов функций классов в центральном элементе, который предоставляет API для конечного пользователя. Теперь у меня есть 2 разные аппаратные установки, которые в целом очень похожи и имеют очень большое общее подмножество функций, но обе они имеют дополнительные функции для регистрации.

Итак, я поместил общее подмножество функциональности в абстрактный базовый класс (в следующем примере «Bar»), который наследуется от Mixin. Оба аппаратных класса ("Foo" и "Foo2") наследуются от "Bar", но также и от Mixin, что приводит к двойному вызову Mixin. init () во время фазы инициализации любого из "Foo" "или" Foo2 "

from abc import ABC, abstractmethod
import inspect


class FunctionHandler:
    function_register = dict()

    @staticmethod
    def register_function(stuff, register):
        if register not in FunctionHandler.function_register.keys():
            FunctionHandler.function_register[register] = list()

        FunctionHandler.function_register[register].append(stuff)


class Mixin(ABC):
    def __init__(self):
        ABC.__init__(self)
        # only call mix in the highest level of inheritance
        if not inspect.stack()[2].function == "__init__":
            self.mix()

    @abstractmethod
    def mix(self):
        raise NotImplementedError

    def register_stuff(self, stuff, register):
        FunctionHandler.register_function(stuff, register)


class Bar(Mixin, ABC):
    def __init__(self):
        ABC.__init__(self)
        self.name = "bar"
        Mixin.__init__(self)

    def mix(self):
        # register a lot of stuff common to all inheriting classes
        self.register_stuff("bar", self.name)
        self.register_stuff("bar2", self.name)


class Foo(Bar, Mixin):
    def __init__(self):
        Bar.__init__(self)
        self.name = "foo"

        Mixin.__init__(self)

    def mix(self):
        Bar.mix(self)
        self.register_stuff("foo", self.name)


class Foo2(Bar, Mixin):
    def __init__(self):
        Bar.__init__(self)
        self.name = "foo2"

        Mixin.__init__(self)

    def mix(self):
        Bar.mix(self)
        self.register_stuff("foo2", self.name)

if __name__ == "__main__":
    foo = Foo()
    foo2 = Foo2()

    print(FunctionHandler.function_register)

Мой желаемый результат для этого примера:

{'foo': ['bar', 'bar2', 'foo'], 'foo2': ['bar', 'bar2', 'foo2']}

Двойная инициализация этого миксина приводит к ненужным записям в регистре функций, которых я хотел бы избежать.

Мое текущее решение состоит в том, чтобы вызывать self.mix () из Mixin только в том случае, если вызывающий класс находится на самом верху наследования (для классов, которые не являются базовыми классами другого потомка) с inspect.stack () состояние:

if not inspect.stack()[2].function == "__init__":
    self.mix()

Как вы можете проверить, результат без этого условия:

{'foo2': ['bar', 'bar2', 'foo2'], 'foo': ['bar', 'bar2', 'foo'], 'bar': ['bar', 'bar2', 'foo', 'bar', 'bar2', 'foo2']}

Так что в целом мое решение работает, однако я не полностью им удовлетворен, так как условие сравнения строк в стеке вызовов кажется мне немного небезопасным.

Так что для меня есть 2 вопроса:

  1. Разве моя концепция не оптимальна, так что этот двойной Mixin. init () происходит в первую очередь? Если да, как эта структура могла бы быть реализована лучше?

  2. Если концепция в целом в порядке, есть ли способ проверить «глубину наследования», чтобы я мог проверить это вместо имени функции стека вызовов?

1 Ответ

0 голосов
/ 17 июня 2019

Проблема не в двойном вызове Mixin.__init__(), а в том, что в этом методе есть метод mix(), поэтому он вызывается 2 раза вместо 1 (потому что Bar вызывает его, а затем Foo и Foo2позвони еще раз).

Чтобы устранить это поведение, лучше вызывать mix() внутри классов листьев, поэтому методы Foo.__init__() и Foo2.__init__() должны выглядеть следующим образом:

class Mixin(ABC):
    def __init__(self):
        ABC.__init__(self)
        # end of __init__

class Foo(Bar, Mixin):
    def __init__(self):
        Bar.__init__(self)
        self.name = "foo"
        Mixin.__init__(self)
        self.mix()

    def mix(self):
        Bar.mix(self)
        self.register_stuff("foo", self.name)

Результаттак же, как вы хотели:

{'foo': ['bar', 'bar2', 'foo'], 'foo2': ['bar', 'bar2', 'foo2']}


РЕДАКТИРОВАНИЕ Если вы хотите оставить метод mix внутри Mixin.__init__(), вы можете передать аргумент объектав конструктор и вызовите в нем mix ().

class Mixin(ABC):
    def __init__(self, obj=None):
        ABC.__init__(self)
        if obj and isinstance(obj, Mixin):
            obj.mix()

class Foo(Bar, Mixin):
    def __init__(self):
        Bar.__init__(self)
        self.name = "foo"
        Mixin.__init__(self, self)

Вывод остался прежним

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...