Подтвердить что ты не робот

Python: Правильный способ инициализации, когда суперклассы принимают разные аргументы?

Если у меня есть три класса:

class BaseClass(object):
    def __init__(self, base_arg, base_arg2=None):
        ...

class MixinClass(object):
    def __init__(self, mixin_arg):
        ...

class ChildClass(BaseClass, MixinClass):
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        ???

Каков правильный способ инициализации MixinClass и BaseClass?

Не похоже, что я могу использовать super, потому что MixinClass и BaseClass оба принимают разные аргументы... И два вызова MixinClass.__init__(...) и BaseClass.__init__(...) могут вызвать проблему наследования алмазов super предназначен для предотвращения.

4b9b3361

Ответ 1

В принципе, в Python вы не можете безопасно поддерживать этот тип наследования. К счастью, вам почти никогда не нужно, поскольку большинство методов не волнует, что-то есть, только то, что поддерживает определенный интерфейс. Лучше всего использовать композицию или агрегацию: наследовать свой класс от одного из родительских классов и содержать ссылку на экземпляр второго класса. Пока ваш класс поддерживает интерфейс второго класса (и пересылает сообщения в содержащийся экземпляр), это, вероятно, будет работать нормально.

В приведенном выше примере (где оба класса наследуются от object) вы можете (вероятно) надежно просто наследовать от обоих классов и называть оба конструктора с помощью MixinClass.__init__ и BaseClass.__init__. Но обратите внимание, что небезопасно делать, если родительские классы называют super в своих конструкторах. Хорошим правилом является использование super, если родительские классы используют super и __init__, если родительские классы используют __init__, и надеюсь, что вы никогда не пойманы в ловушку, чтобы наследовать от двух классов, которые выбрали разные методы для инициализации.

Ответ 2

Чтобы увеличить правильный ответ от @chris-b, следуйте некоторым примерам, основанным на примере использования OP, и шаблоне, который я уже пробовал (и в большинстве случаев потерпел неудачу, по крайней мере, в соответствии с красотой кода).

резюме

Как итог, если вы вызываете super.__init__ в каждом классе init, пишите, python будет хорошо следовать MRO, чтобы вызывать все inits для всех классов, когда используется множественное наследование. super.__init__ работает с вызовом parent.__init__ и делегирует parent.__init__ для вызова всех его дочерних элементов.

Из этого следует, что для простого class C(A, B), B.__init__ будет вызываться только в том случае, если A.__init__ сам вызывает super.__init__, даже если C.__init__ использует super.__init__.

Альтернативой является ручное вызов требуемых inits, например. A.__init__(self) и B.__init__(self) в C.__init__; недостатком является то, что этот шаблон потенциально разрушает будущие унаследованные классы, которые называют super, и ожидают, что все родительские inits также будут вызваны. Нужно/знать/что делают различные родительские интины.

Таким образом, можно подумать, что использование super.__init__ все время - правильная вещь; но, как указывает OP, эта "магическая" цепочка вызовов ломается, когда разные аргументы ожидаются от другого init (обычная вещь с шаблоном mixin!).

Больше информации можно найти в Как работает супер() Python с множественным наследованием?

Есть ли идеальное решение?

К сожалению. кажется, что в Python использование множественного наследования (и шаблонов mixin) требует некоторого знания того, что происходит на нескольких уровнях;

даже пытаясь планировать расширенные случаи, принимая *args и **kwargs, а вызов super.__init__, передающий все аргументы, будет терпеть неудачу, потому что объект. init() принимает только один параметр (self)!

Это показано в первом примере ниже.

Чрезвычайно уродливый хак, который я использовал, и который работает (хотя и потенциально не для всех возможных ситуаций), заключается в том, чтобы обернуть вызовы super. init в try, за исключением таких блоков, как:

try:
    super(ThisClass, self).__init__(*args, arg1=arg1, arg2=arg2, **kwargs)
except TypeError as e:
    # let hope this TypeError is due to the arguments for object...
    super(ThisClass, self).__init__()

Это похоже на работу - но действительно уродливое.

Я сделал суть: https://gist.github.com/stefanocrosta/1d113a6a0c79f853c30a64afc4e8ba0a

но на всякий случай это примеры:

Полный пример 1

class BaseClass(object):
    def __init__(self, base_arg, base_arg2=None, *args, **kwargs):
        print "\tBaseClass: {}, {}".format(base_arg, base_arg2)
        super(BaseClass, self).__init__(*args, base_arg=base_arg, base_arg2=base_arg2, **kwargs)

class MixinClass(object):
    def __init__(self, mixin_arg, *args, **kwargs):
        print "\tMixinClass: {}".format(mixin_arg)
        super(MixinClass, self).__init__()

class MixinClassB(object):
    def __init__(self, mixin_arg, *args, **kwargs):
        print "\tMixinClassB: {}".format(mixin_arg)
        super(MixinClassB, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)

class ChildClassA(BaseClass, MixinClass):
    """
    Let make it work for this case
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassA, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)

class ChildClassB(BaseClass, MixinClass):
    """
    Same as above, but without specifying the super.__init__ arguments names
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        # If you don't specify the name of the arguments, you need to use the correct order of course:
        super(ChildClassB, self).__init__(base_arg, base_arg2, mixin_arg)

class ChildClassC(BaseClass, MixinClassB, MixinClass):
    """
    Now let simply add another mixin: before...
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassC, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)

class ChildClassD(BaseClass, MixinClass, MixinClassB):
    """
    Now let simply add another mixin: ..and after
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassD, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)        

childA = ChildClassA(1, 3, 2)  # note the order of the arguments - the mixin arg is interleaved
childB = ChildClassB(1, 3, 2)
childC = ChildClassC(1, 3, 2)
childD = ChildClassD(1, 3, 2)

Полный пример 2:

class BaseClass(object):
    def __init__(self, base_arg, base_arg2=None, *args, **kwargs):
        print "\tBaseClass: {}, {}".format(base_arg, base_arg2)
        try:
            super(BaseClass, self).__init__(*args, base_arg=base_arg, base_arg2=base_arg2, **kwargs)
        except:
            super(BaseClass, self).__init__()

class MixinClass(object):
    def __init__(self, mixin_arg, *args, **kwargs):
        print "\tMixinClass: {}".format(mixin_arg)
        try:
            super(MixinClass, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)
        except:
            super(MixinClass, self).__init__()

class MixinClassB(object):
    def __init__(self, mixin_arg, *args, **kwargs):
        print "\tMixinClassB: {}".format(mixin_arg)
        try:
            super(MixinClassB, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)
        except:
            super(MixinClassB, self).__init__()

class ChildClassA(BaseClass, MixinClass):
    """
    Let make it work for this case
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassA, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)        


class ChildClassC(BaseClass, MixinClassB, MixinClass):
    """
    Now let simply add another mixin: before...
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassC, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)

class ChildClassD(BaseClass, MixinClass, MixinClassB):
    """
    Now let simply add another mixin: ..and after
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassD, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)        

try:
    base = BaseClass(1, 2)
except Exception as e:
    print "Failed because object.__init__ does not expect any argument ({})".format(e)
childA = ChildClassA(1, 3, 2)  # note the order of the arguments - the mixin arg is interleaved
childC = ChildClassC(1, 3, 2)
childD = ChildClassD(1, 3, 2)