Кажется, нет хорошей онлайн-документации по этому поводу: если я создам производный класс, будет ли он автоматически иметь все атрибуты базового класса? Но для чего нужен BaseClass.__init()
, нужно ли делать это и с другими методами базового класса? Нужны ли аргументы BaseClass.__init__()
? Если у вас есть аргументы для вашего базового класса __init__()
, используются ли они также производным классом, вам нужно явно установить аргументы для __init__()
производного класса или вместо этого установить их в BaseClass.__init__()
?
Имеет ли производный класс автоматически все атрибуты базового класса?
Ответы (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.
Bar
расширяет Foo
атрибутом c
. Foo
не может использовать ключевое слово c
. Если я что-то пропустил?
- person Henry Gomersall; 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
и просто передаете аргументы базовому классу без изменений, копируете сигнатуру базового класса или предоставляете аргументы откуда-то еще, зависит от того, чего вы пытаетесь достичь.