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

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

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 предназначен для предотвращения.

Ответы [ 2 ]

13 голосов
/ 27 октября 2010

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

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

1 голос
/ 01 декабря 2016

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

recap

Таким образом, если вы наберете 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__.

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

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

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

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

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

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

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

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

try:
    super(ThisClass, self).__init__(*args, arg1=arg1, arg2=arg2, **kwargs)
except TypeError as e:
    # let's 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's 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's 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's 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's 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's 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's 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...