Имеет ли производный класс автоматически все атрибуты базового класса? - PullRequest
23 голосов
/ 18 июня 2011

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

Ответы [ 2 ]

57 голосов
/ 18 июня 2011

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

11 голосов
/ 18 июня 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...