Имеет ли производный класс автоматически все атрибуты базового класса?

Кажется, нет хорошей онлайн-документации по этому поводу: Если я создам производный класс, автоматически ли он будет иметь все атрибуты базового класса? Но что за BaseClass.__init() для, вам также нужно сделать это с другими методами базового класса? Требуются ли аргументы BaseClass.__init__()? Если у вас есть аргументы для вашего базового класса __init__(), они также используются производным классом, нужно ли явно указывать аргументы производному классу __init__() или вместо них вместо BaseClass.__init__()?

Ответ 1

Если вы реализуете __init__ в классе, производном от BaseClass, то он перезапишет унаследованный метод __init__, и поэтому BaseClass.__init__ никогда не будет вызван. Если вам нужно вызвать метод __init__ для BaseClass (как это обычно бывает), то это зависит от вас и сделайте это явно, вызвав BaseClass.__init__, как правило, из недавно реализованного метода __init__.

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

    def do_something(self):
        print self.a

class Bar(Foo):
    def __init__(self):
        self.b = 20

bar = Bar()
bar.do_something()

Это приведет к следующей ошибке:

AttributeError: 'Bar' object has no attribute 'a'

Итак, метод do_something был унаследован, как и ожидалось, но для этого метода требуется, чтобы атрибут a был установлен, что никогда не происходит из-за того, что __init__ также был перезаписан. Мы обходим это путем явного вызова Foo.__init__ из Bar.__init__.

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

    def do_something(self):
        print self.a

class Bar(Foo):
    def __init__(self):
        Foo.__init__(self)
        self.b = 20

bar = Bar()
bar.do_something()

который печатает 10, как ожидалось. Foo.__init__ в этом случае ожидает единственный аргумент, являющийся экземпляром Foo (который по соглашению называется self).

Обычно, когда вы вызываете метод в экземпляре класса, экземпляр класса автоматически передается в качестве первого аргумента. Методы для экземпляра класса называются связанными методами. bar.do_something - пример связанного метода (и вы заметите, что он вызывается без каких-либо аргументов). Foo.__init__ является несвязанным методом, поскольку он не привязан к конкретному экземпляру Foo, поэтому первый аргумент, экземпляр Foo, должен быть явно передан.

В нашем случае мы передаем self в Foo.__init__, который является экземпляром Bar, который был передан методу __init__ в Bar. Поскольку Bar наследует от Foo, экземпляры Bar также являются экземплярами Foo, поэтому допускается передать self в Foo.__init__.

Вероятно, что класс, который вы наследуете, требует или принимает больше аргументов, чем просто экземпляр класса. Они рассматриваются так же, как и с любым методом, который вы вызываете из __init__:

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

    def do_something(self):
        print self.a

class Bar(Foo):
    def __init__(self):
        Foo.__init__(self, 20)

bar = Bar()
bar.do_something()

который печатает 20.

Если вы пытаетесь реализовать интерфейс, который полностью раскрывает все аргументы инициализации базового класса через ваш наследующий класс, вам нужно будет сделать это явно. Обычно это делается с аргументами * args и ** kwargs (имена по соглашению), которые являются заполнителями для всех остальных аргументов, которые явно не указаны. В следующем примере используется все, что я обсуждал:

class Foo(object):
    def __init__(self, a, b=10):
        self.num = a * b

    def do_something(self):
        print self.num

class Bar(Foo):
    def __init__(self, c=20, *args, **kwargs):
        Foo.__init__(self, *args, **kwargs)
        self.c = c

    def do_something(self):
        Foo.do_something(self)
        print self.c


bar = Bar(40, a=15)
bar.do_something()

В этом случае аргумент c устанавливается равным 40, так как это первый аргумент Bar.__init__. Второй аргумент затем включается в переменные args и kwargs (* и ** - это специфический синтаксис, который говорит, что расширение списка/кортеж или словарь в отдельные аргументы при передаче функции/методу) и передается до Foo.__init__.

В этом примере также указывается, что любой перезаписанный метод должен быть вызван явно, если это то, что требуется (поскольку в этом случае do_something).

В последнем случае вы часто увидите super(ChildClass, self).method() (где ChildClass - произвольный дочерний класс), который используется вместо вызова метода BaseClass явно. Обсуждение super - это целый другой вопрос, но достаточно сказать, что в этих случаях он обычно используется для выполнения именно того, что делается, вызывая BaseClass.method(self). Вкратце, super делегирует вызов метода следующему классу в порядке разрешения метода - MRO (который в одиночном наследовании является родительским классом). Дополнительную информацию см. В документации по супер.

Ответ 2

Если я создам производный класс, автоматически ли он будет иметь все атрибуты базового класса?

Атрибуты класса, да. Атрибуты экземпляра нет (просто потому, что они не существуют при создании класса), если в производном классе нет __init__, и в этом случае вместо него будет вызван базовый, и будут установлены атрибуты экземпляра.

Поддерживает ли BaseClass. init() аргументы?

Зависит от класса и его подписи __init__. Если вы явно вызываете Base.__init__ в производном классе, вам, по крайней мере, нужно будет передать self в качестве первого аргумента. Если у вас есть

class Base(object):
    def __init__(self):
        # something

то довольно очевидно, что никакие другие аргументы не принимаются __init__. Если у вас будет

class Base(object):
    def __init__(self, argument):
        # something

тогда вы должны передать argument при вызове базы __init__. Здесь нет ракетостроения.

Если у вас есть аргументы для базового класса init(), они также используются производным классом, вам нужно явно установить аргументы в производный класс init() или установите их в BaseClass. init() вместо?

Опять же, если производный класс не имеет __init__, вместо него будет использоваться базовый.

class Base(object):
    def __init__(self, foo):
        print 'Base'

class Derived(Base):
    pass

Derived()   # TypeError
Derived(42) # prints Base

В другом случае вам нужно как-то позаботиться об этом. Используете ли вы *args, **kwargs и просто передаете аргументы, не модифицированные в базовый класс, или копируете подпись базового класса или аргументы из других источников, зависят от того, что вы пытаетесь выполнить.