Как в сценарии множественного наследования Python3 передает аргументы всем родительским классам? Рассмотрим игрушку ниже:
class A(object):
def __init__(self, a='x', **kwargs):
print('A', kwargs)
super().__init__()
class B(object):
def __init__(self, **kwargs):
print('B', kwargs)
super().__init__()
class C(A,B):
def __init__(self, **kwargs):
print('C', kwargs)
super().__init__(**kwargs)
c = C(a='A', b='B', c='C')
Результат:
C {'a': 'A', 'b': 'B', 'c': 'C'}
A {'b': 'B', 'c': 'C'}
B {}
Что я ожидаю сделать, так это передать одни и те же kwargs всем родительским классам и позволить им использовать значения по своему усмотрению. Но я вижу, что как только я опускаюсь до первого родительского класса A, kwargs потребляется и ничего не передается B.
Пожалуйста помоги!
Обновлять Если бы порядок наследования был детерминированным и я знал исчерпывающий список kwargs, которые могут быть переданы по цепочке наследования, мы могли бы решить эту проблему так:
class A(object):
def __init__(self, a='x', **kwargs):
print('A', kwargs)
super().__init__(**kwargs)
class B(object):
def __init__(self, **kwargs):
print('B', kwargs)
super().__init__()
class C(A,B):
def __init__(self, **kwargs):
print('C', kwargs)
super().__init__(**kwargs)
c = C(a='A', b='B', c='C')
Здесь, поскольку A передает kwargs и мы знаем, что B будет вызываться после него, мы в безопасности, и к моменту вызова object.__init__() kwargs будут пусты.
Однако это может быть не всегда так.
Рассмотрим этот вариант:
class C(B,A):
def __init__(self, **kwargs):
print('C', kwargs)
super().__init__(**kwargs)
Здесь object.__init__(), вызванный из class A, вызовет исключение, поскольку есть еще kwargs, которые можно использовать.
Итак, есть ли общее руководство по дизайну, которому я должен следовать?
Проверьте, может ли помочь доступный здесь ответ: stackoverflow.com/questions/60495296/…
@wim В этом отношении ты прав. Если бы я передал его как super().__init__(**kwargs) в классе A, то класс B получил бы эти параметры. Однако это будет означать, что я знаю и могу гарантировать, что __init__ из object не получит никаких кваргов. Этого не будет, если я также создал экземпляр другого класса непосредственно из A как: d=A(a='x', b='b'). (Это будет утверждать). Я обновил вопрос, чтобы добавить больше контекста к проблеме.
Да, если вы передаете произвольные аргументы неизвестным родителям, ваш класс должен быть спроектирован как миксин, а не как необязательный автономный класс или миксин. Обычно в иерархии классов каждый класс определяет конкретные параметры и передает только остатки как kwargs или, по крайней мере, обеспечивает удаление используемых kwargs перед передачей остатков родительскому; тогда все кварги должны быть израсходованы, и object получит ноль кваргов, как и должно быть.
@HetalThaker Ответ в упомянутой ссылке имеет смысл. Я также хотел бы понять общий шаблон проектирования. Все, что останется внутри kwargs к тому времени, когда оно достигнет конструктора object, вызовет проблемы, и мы не можем гарантировать (в моем коде), какой класс создает пользователь и какие данные он передает. Например, я не могу вставлять неподдерживаемые ключевые слова в class A, так как они могут использоваться последующими super. Может быть, у меня должен быть базовый класс, который обрабатывает этот случай и извлекает все, прежде чем вызывать его super(), и чтобы все остальные классы наследуются от него?
Вы разрабатываете свои классы для использования определенным образом. Если они используются неправильно, ничего страшного, если это приведет к ошибке. Вам просто нужно использовать свои классы так, как вы их разработали, или четко задокументировать, как они должны использоваться, если вы не собираетесь быть пользователем. Тогда не должно быть большого беспокойства по поводу того, что «возможно, другие классы используют аргумент» или «пользователь создает экземпляр неправильного класса».
@Anshul stackoverflow.com/q/52272811/674039 может быть полезно/дублировать
@wim Да, ответ очень полезен для понимания общего процесса проектирования. То, что предложил Deceze, также в целом похоже. Итак, я резюмирую его ключевые моменты и закрываю этот вопрос. Большое спасибо!
Как было предложено @deceze, а также указано в обсуждении, которым поделился @wim в комментариях к вопросу, вот краткое изложение (дословно):
если вы передаете произвольные аргументы неизвестным родителям, ваш класс должен быть разработан как миксин, а не как необязательный автономный класс или миксин. Обычно в иерархии классов каждый класс определяет конкретные параметры и передает только остатки как kwargs или, по крайней мере, обеспечивает удаление используемых kwargs перед передачей остатков родительскому; тогда все kwargs должны быть израсходованы, и объект получит нулевые kwargs, как и должно быть.
Вы разрабатываете свои классы для использования определенным образом. Если они используются неправильно, ничего страшного, если это приведет к ошибке. Вам просто нужно использовать свои классы так, как вы их разработали, или четко задокументировать, как они должны использоваться, если вы не собираетесь быть пользователем. Тогда не должно быть большого беспокойства по поводу того, что «возможно, другие классы используют аргумент» или «пользователь создает экземпляр неправильного класса».
Резюме: Не слишком обобщайте классы и не пытайтесь сделать их слишком гибкими для сценария «на всякий случай», когда кто-то позже будет их использовать. В таких случаях должна быть надлежащая документация для лица, расширяющего классы.
Ну, в A вы использовали super().__init__(), но если бы вы хотели передать их, вы бы использовали super().__init__(a=a, **kwargs) вместо этого. Вам решать, передавать их (или нет), здесь нет никакой магии.