Должен ли __init __ () вызывать родительский класс __init __ ()?


132

Я использовал это в Objective-C, у меня есть эта конструкция:

- (void)init {
    if (self = [super init]) {
        // init class
    }
    return self;
}

Должен ли Python также вызывать реализацию родительского класса для __init__?

class NewClass(SomeOtherClass):
    def __init__(self):
        SomeOtherClass.__init__(self)
        # init class

Это также верно / неверно для __new__()и __del__()?

Изменить: есть очень похожий вопрос: наследование и переопределение __init__в Python


вы значительно изменили свой код. Я понимаю, что оригинал objectбыл опечаткой. Но теперь у вас даже нет superназвания, к которому относится ваш вопрос.
— SilentGhost, 06

Я просто подумал, что super используется как имя родительского класса. Я не думал, что кто-то подумает об этой функции. Прошу прощения за недоразумения.
— Георг Шелли,

Почему бы не задать вопрос об автоматическом
— супервызове

Ответы:


67

В Python вызов суперкласса не __init__является обязательным. Если вы его вызываете, то также необязательно, использовать ли superидентификатор или явно указать суперкласс:

object.__init__(self)

В случае объекта вызов супер-метода не является строго необходимым, поскольку супер-метод пуст. То же самое для __del__.

С другой стороны, для __new__, вы действительно должны вызвать супер-метод и использовать его возврат в качестве вновь созданного объекта - если вы явно не хотите вернуть что-то другое.


Значит, нет никакого соглашения просто вызывать реализацию super?
— Георг Шелли,

5
В классах старого стиля вы могли вызвать super init только в том случае, если для суперкласса действительно был определен init (чего часто не происходит). Поэтому люди обычно думают о вызове суперметода, а не из принципа.
— Martin v. Löwis,

1
Если бы синтаксис в python был таким простым [super init], он был бы более распространенным. Просто спекулятивная мысль; суперконструкция в Python 2.x мне немного неудобна.
— u0b34a0f6ae

Вот интересный (и, возможно, противоречивый) пример: bytes.com/topic/python/answers/… init
— mlvljr 07

"optional" в том смысле, что вам не нужно его называть, но если вы его не назовете, он не будет вызван автоматически.
— McKay

140

Если вам нужно сделать что-то из super __init__в дополнение к тому, что делается в текущем классе, __init__,вы должны вызвать это самостоятельно, поскольку это не произойдет автоматически. Но если от super'а ничего __init__,не надо называть , не надо. Пример:

>>> class C(object):
        def __init__(self):
            self.b = 1


>>> class D(C):
        def __init__(self):
            super().__init__() # in Python 2 use super(D, self).__init__()
            self.a = 1


>>> class E(C):
        def __init__(self):
            self.a = 1


>>> d = D()
>>> d.a
1
>>> d.b  # This works because of the call to super's init
1
>>> e = E()
>>> e.a
1
>>> e.b  # This is going to fail since nothing in E initializes b...
Traceback (most recent call last):
  File "<pyshell#70>", line 1, in <module>
    e.b  # This is going to fail since nothing in E initializes b...
AttributeError: 'E' object has no attribute 'b'

__del__ точно так же (но не стоит полагаться на __del__ финализацию - подумайте о том, чтобы сделать это с помощью оператора with).

Я редко использую __new__. Я делаю всю инициализацию в__init__.


3
Определение класса D (C) должно быть исправлено вот такsuper(D,self).__init__()
— eyquem

11
super () .__ init __ () работает только в Python 3. В Python 2 вам нужен super (D, self) .__ init __ ()
— Джасинда

«Если вам нужно что-то из инициализации super ...» - это очень проблематичное утверждение, потому что это не тот случай, нужно ли вам / подклассу «что-то», а нужно ли что-то базовому классу, чтобы быть действительным экземпляр базового класса и работать правильно. Как разработчик производного класса, внутренние компоненты базового класса - это вещи, которые вы не можете / не должны знать, и даже если вы это сделаете, потому что вы написали оба или внутренние компоненты задокументированы, конструкция базы может измениться в будущем и сломаться из-за плохо написанного производный класс. Поэтому всегда проверяйте, что базовый класс инициализирован полностью.
— Ник

105

В ответе Анона:
«Если вам нужно сделать что-то из суперспособности __init__в дополнение к тому, что делается в текущем классе,__init__ , вы должны вызвать это самостоятельно, поскольку это не произойдет автоматически»

Это невероятно: он формулирует прямо противоположную принципу наследования.


Дело не в том, что «что-то из super __init__ (...) не произойдет автоматически» , а в том, что это БУДЕТ происходить автоматически, но этого не происходит, потому что базовый класс '__init__ отменяется определением производного класса.__init__

Итак, ПОЧЕМУ определение производного_класса? __init__ , поскольку он перекрывает то, на что нацелено, когда кто-то прибегает к наследованию ??

Это потому, что нужно определить что-то, что НЕ выполняется в базовом классе __init__, и единственная возможность получить это - поместить его выполнение в функцию производного класса __init__.
Другими словами, в базовом классе нужно что-то « __init__в дополнение к тому, что автоматически делалось бы в базовом классе », __init__если бы последний не был переопределен.
НЕ наоборот.


Тогда проблема в том, что желаемые инструкции, присутствующие в базовом классе __init__, больше не активируются в момент создания экземпляра. Чтобы компенсировать эту инактивацию, требуется кое-что особенное: явный вызов базового класса __init__, «чтобы СОХРАНИТЬ , НЕ ДОБАВИТЬ, инициализацию, выполняемую базовым классом» __init__. Это именно то, что сказано в официальном документе:

Переопределяющий метод в производном классе может фактически захотеть расширить, а не просто заменить одноименный метод базового класса . Существует простой способ вызвать метод базового класса напрямую: просто вызовите BaseClassName.methodname (self, arguments).
http://docs.python.org/tutorial/classes.html#inheritance

Вот и вся история:

  • когда цель состоит в том, чтобы СОХРАНИТЬ инициализацию, выполняемую базовым классом, то есть чистое наследование, ничего особенного не требуется, нужно просто избегать определения __init__функции в производном классе

  • когда целью является ЗАМЕНИТЬ инициализацию, выполняемую базовым классом, она __init__должна быть определена в производном классе

  • когда цель состоит в том, чтобы ДОБАВИТЬ процессы к инициализации, выполняемой базовым классом, __init__ должен быть определен производный класс , содержащий явный вызов базового класса__init__


Что меня удивляет в посте Анона, так это не только то, что он выражает противоположное теории наследования, но и то, что 5 парней прошли мимо, за которые проголосовали, не поворачиваясь, и, более того, никто не отреагировал за 2 года в ветка, интересная тема которой должна читаться относительно часто.


1
Я был уверен, что за этот пост проголосуют. Боюсь, что у меня не будет много объяснений причин, почему. Проще проголосовать против, чем анализировать текст, который кажется непонятным. Я долго пытался понять сообщение Anon, прежде чем я наконец понял, что он написан явно и не очень авторитетно. Может быть, это можно трактовать как приблизительно правильное для того, кто знает о наследстве; но я нахожу это сбивающим с толку, когда читаю кого-то, у кого нетвердые представления о наследовании, предмете не столь ясном, как каменная вода в целом
— eyquem

2
«Я был уверен, что этот пост будет одобрен ...» Основная проблема в том, что вы немного опоздали на вечеринку, и большинство людей могут не читать дальше нескольких первых ответов. Кстати,
— отличное

Отличный ответ.
— Триларион

3
Вы пропустили важные слова «дополнительно» в предложении, которое вы цитировали из Аарона. Утверждение Аарона полностью верно и соответствует тому, что вы говорите.
— GreenAsJade

1
Это первое объяснение, которое сделало выбор в пользу дизайна Python разумным.
— Джозеф Гарвин

20

Изменить : (после изменения кода)
Мы не можем сказать вам, нужно ли вам вызывать родительскую __init__(или любую другую функцию). Очевидно, что наследование работало бы без такого вызова. Все зависит от логики вашего кода: например, если вы все __init__делаете в родительском классе, вы можете просто __init__вообще пропустить дочерний класс .

рассмотрим следующий пример:

>>> class A:
    def __init__(self, val):
        self.a = val


>>> class B(A):
    pass

>>> class C(A):
    def __init__(self, val):
        A.__init__(self, val)
        self.a += val


>>> A(4).a
4
>>> B(5).a
5
>>> C(6).a
12

Я удалил супервызов из своего примера, потому что хотел знать, следует ли вызывать реализацию родительского класса init или нет.
— Георг Шелли,

тогда вы можете захотеть отредактировать заголовок. но мой ответ все еще в силе.
— SilentGhost, 06

5

Нет жесткого правила. Документация для класса должна указывать, должны ли подклассы вызывать метод суперкласса. Иногда вы хотите полностью заменить поведение суперкласса, а в других случаях усилить его - т.е. вызвать свой собственный код до и / или после вызова суперкласса.

Обновление: та же базовая логика применяется к любому вызову метода. Конструкторы иногда нуждаются в особом внимании (поскольку они часто устанавливают состояние, которое определяет поведение) и деструкторы, поскольку они параллельны конструкторам (например, при распределении ресурсов, например, при подключении к базе данных). Но то же самое может относиться, скажем, кrender() методу виджета.

Дальнейшее обновление: что такое OPP? Вы имеете в виду ООП? Нет - подклассу часто нужно что- то знать о структуре суперкласса. Не внутренние детали реализации, а базовый контракт суперкласса со своими клиентами (с использованием классов). Это никоим образом не нарушает принципы ООП. Вот почему protectedэто правильная концепция в ООП в целом (хотя, конечно, не в Python).


Вы сказали, что иногда хочется вызвать собственный код перед вызовом суперкласса. Для этого нужно знать реализацию родительского класса, что нарушит OPP.
— Георг Шелли

4

ИМО, вы должны это назвать. Если ваш суперкласс есть object, вам не следует этого делать, но в других случаях я считаю исключительным не называть его. Как уже ответили другие, очень удобно, если вашему классу даже не нужно переопределять __init__себя, например, когда у него нет (дополнительного) внутреннего состояния для инициализации.


2

Да, вы всегда должны __init__явно вызывать базовый класс в качестве хорошей практики кодирования. Если вы забудете об этом, это может вызвать небольшие проблемы или ошибки во время выполнения. Это верно, даже если__init__ не принимает никаких параметров. Это не похоже на другие языки, где компилятор неявно вызывает за вас конструктор базового класса. Python этого не делает!

Основная причина постоянного вызова базового класса _init__заключается в том, что базовый класс обычно может создавать переменные-члены и инициализировать их значениями по умолчанию. Поэтому, если вы не вызовете init базового класса, ни один из этих кодов не будет выполнен, и вы получите базовый класс, не имеющий переменных-членов.

Пример :

class Base:
  def __init__(self):
    print('base init')

class Derived1(Base):
  def __init__(self):
    print('derived1 init')

class Derived2(Base):
  def __init__(self):
    super(Derived2, self).__init__()
    print('derived2 init')

print('Creating Derived1...')
d1 = Derived1()
print('Creating Derived2...')
d2 = Derived2()

Это печатает ..

Creating Derived1...
derived1 init
Creating Derived2...
base init
derived2 init

Запустите этот код .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.