Должен ли __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 года в ветка, интересная тема которой должна читаться относительно часто.

Eyquem
источник
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
SilentGhost
источник
Я удалил супервызов из своего примера, потому что хотел знать, следует ли вызывать реализацию родительского класса init или нет.
Георг Шелли,
тогда вы можете захотеть отредактировать заголовок. но мой ответ все еще в силе.
SilentGhost, 06
5

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

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

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

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

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

u0b34a0f6ae
источник
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

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

Шиталь шах
источник