Ваш опубликованный код даже не компилируется, а тем более запускается. Но, догадываясь, как это должно работать ...
Да, у вас все в порядке.
Но вы должны убедиться в этом сами, двумя способами. И знать, как это проверить, может быть даже важнее, чем знать ответ.
Сначала просто распечатайте Thief.mro()
. Это должно выглядеть примерно так:
[Thief, Agile, Sneaky, Character, object]
И затем вы можете увидеть, какие классы предоставляют метод __init__
, и, следовательно, как они будут объединены в цепочку, если все просто вызовут super
:
>>> [cls for cls in Thief.mro() if '__init__' in cls.__dict__]
[Agile, Sneaky, Character, object]
И, просто чтобы убедиться, что Agile
действительно вызывается первым:
>>> Thief.__init__
<function Agile.__init__>
Во-вторых, вы можете запустить свой код в отладчике и выполнить вызовы.
Или вы можете просто добавить print
операторов вверху и внизу каждого из них, например:
def __init__(self, agile=True, *args, **kwargs):
print(f'>Agile.__init__(agile={agile}, args={args}, kwargs={kwargs})')
super().__init__(*args, **kwargs)
self.agile = agile
print(f'<Agile.__init__: agile={agile}')
(Вы могли бы даже написать декоратор, который делает это автоматически, с небольшим количеством inspect
волшебства.)
Если вы сделаете это, он напечатает что-то вроде:
> Agile.__init__(agile=True, args=(), kwargs={'name': 'Parker', 'sneaky':False})
> Sneaky.__init__(sneaky=False, args=(), kwargs={'name': 'Parker'})
> Character.__init__(name='Parker', args=(), kwargs={})
< Character.__init__: name: 'Parker'
< Sneaky.__init__: sneaky: False
< Agile.__init__: agile: True
Итак, вы правы насчет порядка, который вызывается через super
, и порядок, в котором стек возвращается на обратном пути, явно противоположен.
Но, между тем, вы ошиблись одной деталью:
отправляется в класс Sneaky (через super), где произойдет то же самое - все аргументы распаковываются, с перекрестными ссылками на параметры Sneaky (это когда sneaky = False установлен)
Здесь задается параметр / локальная переменная sneaky
, но self.sneaky
не устанавливается до после , когда возвращается super
. До этого (включая Character.__init__
и аналогично для любых других миксинов, которые вы выберете после Sneaky
), sneaky
в self.__dict__
не будет, поэтому, если кто-нибудь попытается найти self.sneaky
они смогут найти только атрибут класса, который имеет неправильное значение.
Что поднимает другой вопрос: для чего эти атрибуты класса? Если вы хотите, чтобы они предоставили значения по умолчанию, у вас уже есть значения по умолчанию в параметрах инициализатора для этого, поэтому они бесполезны.
Если вы хотели, чтобы они указывали значения во время инициализации, то они потенциально неверны, поэтому они хуже, чем бесполезны. Если вам нужно набрать self.sneaky
перед вызовом Character.__init__
, способ сделать это прост: просто переместите self.sneaky = sneaky
вверх до super()
вызова.
Фактически, это одна из сильных сторон "явной super
" модели Python. В некоторых языках, таких как C ++, конструкторы всегда вызываются автоматически, будь то изнутри или снаружи. Python заставляет вас делать это явно, это менее удобно и труднее ошибиться, но это означает, что вы можете выполнить настройку либо раньше или после того, как базовый класс получит свой шанс (или, конечно, немного от каждого), что иногда полезно.