Может ли Super справиться с множественным наследованием?

При наследовании от двух объектов, подобных этим

class Foo(object):
  def __init__(self,a):
    self.a=a

class Bar(object):
  def __init__(self,b):
    self.b=b

Я бы обычно делал что-то вроде этого

class FooBar(Foo,Bar):
  def __init__(self,a,b):
    Foo.__init__(self,a)
    Bar.__init__(self,b)

Как супер знаю, хочу ли я позвонить и тому и другому? и если да, то как он узнает, какой аргумент пройдет где. Или это просто невозможно для пользователя здесь?

Даже если Foo и Bar принимают те же аргументы, может ли super справиться с этим?

Или я не должен пытаться сделать это в первую очередь?

Ответ 1

Использование super(), __init__() и множественное наследование немного сложнее.

В обычном потоке вещей каждый метод вызывает super(), а классы, которые наследуют только от object, выполняют небольшую дополнительную работу, чтобы убедиться, что метод действительно существует: он будет выглядеть примерно так:

>>> class Foo(object):
...     def frob(self, arg):
...         print "Foo.frob"
...         if hasattr(super(Foo, self), 'frob'):
...             super(Foo, self).frob(arg)
... 
>>> class Bar(object):
...     def frob(self, arg):
...         print "Bar.frob"
...         if hasattr(super(Bar, self), 'frob'):
...             super(Bar, self).frob(arg)
... 
>>> class Baz(Foo, Bar):
...     def frob(self, arg):
...         print "Baz.frob"
...         super(Baz, self).frob(arg)
... 
>>> b = Baz()
>>> b.frob(1)
Baz.frob
Foo.frob
Bar.frob
>>> 

Но когда вы пытаетесь сделать что-то подобное по методу, который на самом деле имеет object, все становится немного рискованным; object.__init__ не принимает аргументов, поэтому о единственном безопасном способе использовать это вызов super().__init__() без аргументов, так как этот вызов может обрабатываться object.__init__. Но тогда это может не обрабатываться object.__init__, а вместо этого обрабатываться классом в другом месте в графе наследования. Таким образом, любой класс, который определяет __init__ в иерархии класса множественного наследства, должен быть готов к вызову без аргументов.

Один из способов борьбы с этим - никогда не использовать аргументы в __init__(). Выполняйте минимальную инициализацию и полагайтесь на настройку свойств или используя другие средства для настройки нового объекта перед использованием. Это довольно неприятно, однако.

Другой способ - использовать только аргументы ключевого слова, что-то вроде def __init__(self, **keywords): и всегда удалять аргументы, относящиеся к данному конструктору. Это стратегия на основе надежда, вы надеетесь, что все ключевые слова будут потребляться до того, как управление достигнет уровня object.__init__.

Третий способ - определить суперкласс для всех многонаследованных баз, который сам определяет __init__ каким-то полезным способом и не вызывает super().__init__ (object.__init__ - это no-op). Это означает, что вы можете быть уверены, что этот метод всегда называется последним, и вы можете делать то, что вам нравится, с вашими аргументами.

Ответ 2

Или я не должен пытаться сделать это в первую очередь?

Правильно. Вы должны вызывать только одну родительскую строку __init__ s, а не дерево. super будет использовать MRO для выполнения поиска по глубине родительских классов, ищущих подходящий __init__ для вызова. Он не будет и не должен вызывать все возможные __init__ s.