避免多次调用Mixin类的__init__

时间:2019-06-17 16:04:52

标签: python python-3.x inheritance multiple-inheritance

我正在处理一个大型结构,该结构正在与Mixin一起将类的函数句柄注册到向最终用户提供API的中央元素上。我现在有2种不同的硬件设置,它们总体上非常相似,并且具有非常大的通用功能子集,但是它们两者都有要注册的附加功能。

因此,我将功能的公共子集放入了继承自Mixin的抽象基类(在下面的示例中为“ Bar”)。硬件类(“ 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']}

对此Mixin进行两次初始化会导致在函数寄存器中出现不必要的条目,我想避免这些条目。

我当前的解决方案是,如果调用类位于继承的最顶层(对于不是另一个子级的基类的类),则仅使用inspect.stack()来调用Mixin的self.mix()。条件:

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

您可以检查,没有这种情况的结果是:

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

因此,一般而言,我的解决方案有效,但是我并不完全满意,因为调用堆栈上的字符串比较条件对我来说似乎有点不安全。

所以对我来说有两个问题:

  1. 我的概念是否不是最优的,以至于这种双重Mixin。 init ()调用首先发生?如果可以,如何更好地实现这种结构?

  2. 如果总体上这个概念还可以,有没有办法检查“继承深度”,以便我可以检查它,而不是调用堆栈的函数名?

1 个答案:

答案 0 :(得分:0)

问题不在于对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)

输出仍然相同