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

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


person liamel1i    schedule 18.06.2011    source источник


Ответы (2)


Если вы реализуете __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 (который при одиночном наследовании является родительским классом). Дополнительные сведения см. в документации по super.

person Henry Gomersall    schedule 18.06.2011
comment
@PM2Ring, что ты имеешь в виду? - person Henry Gomersall; 21.01.2019
comment
@ PM2Ring Нет, в том-то и дело. Bar расширяет Foo атрибутом c. Foo не может использовать ключевое слово c. Если я что-то пропустил? - person Henry Gomersall; 22.01.2019
comment
Извините, похоже, я немного запутался (и я не мог легко проверить это на своем телефоне). - person PM 2Ring; 22.01.2019

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

Атрибуты класса, да. Атрибуты экземпляра, нет (просто потому, что они не существуют при создании класса), если только в производном классе нет __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 и просто передаете аргументы базовому классу без изменений, копируете сигнатуру базового класса или предоставляете аргументы откуда-то еще, зависит от того, чего вы пытаетесь достичь.

person Cat Plus Plus    schedule 18.06.2011