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

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

24
задан martineau 20 January 2019 в 23:10
поделиться

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

11
ответ дан 28 November 2019 в 22:32
поделиться

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

58
ответ дан 28 November 2019 в 22:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: