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