Чи повинен __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

Я просто подумав, що супер використовується як назва батьківського класу. Я не думав, що хтось подумає про функцію. Вибачте за будь-які непорозуміння.
Георг Шоллі

Чому не автоматичний питання супер виклику: stackoverflow.com/questions/3782827 / ...
Чіро Сантіллі郝海东冠状病六四事件法轮功

Відповіді:


67

У Python виклик суперкласу ' __init__необов’язковий. Якщо ви її називаєте, то також необов'язково використовувати superабо ідентифікатор, або явно називати суперклас:

object.__init__(self)

У випадку об'єкта викликати супер метод не є строго необхідним, оскільки супер метод порожній. Те саме для __del__.

З іншого боку, бо __new__, ви дійсно повинні викликати супер-метод і використовувати його повернення як новостворений об'єкт - якщо тільки ви явно не хочете повернути щось інше.


Тож немає ніякої конвенції просто називати реалізацію super?
Георг Шоллі

5
У класах старого стилю ви могли називати суперінітант лише у тому випадку, якщо для суперкласу був визначений ініт (який часто не має). Тому люди зазвичай думають про те, щоб викликати супер метод, а не робити це поза принципом.
Мартін проти Левіса

1
Якби синтаксис у python був настільки ж простим, як [super init]це було б більш часто. Просто умоглядна думка; суперконструкція в Python 2.x трохи незручна для мене.
u0b34a0f6ae

Ось, здається, цікавий (і, можливо, суперечливий) приклад: bytes.com/topic/python/answers/… init
mlvljr

"необов'язково", тому що вам не потрібно його називати, але якщо ви не зателефонуєте, він не буде викликаний автоматично.
Маккей

140

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

"Якщо вам потрібно щось із init супер ..." - Це дуже проблематичне твердження, оскільки справа не в тому, чи потрібно вам / підкласу "щось", а чи потрібен базовий клас для того, щоб бути дійсним Екземпляр базового класу і правильно працювати. Як реалізатор похідного класу, внутрішні бази базового класу - це речі, які ви не можете / не повинні знати, і навіть якщо ви це робите, тому що ви написали те або інше, чи внутрішні документи задокументовані, дизайн бази може змінитися в майбутньому і зламатися через погано написане похідний клас. Тому завжди переконайтеся, що базовий клас ініціалізований повністю.
Нік

105

У відповідь Анона:
"Якщо вам потрібно зробити щось із супер- __init__класу __init__, крім того, що робиться в поточному класі , ви повинні зателефонувати самі, оскільки це не відбудеться автоматично"

Це неймовірно: він формулює точно протилежний принцип успадкування.


Це не те, що "щось із супер __init__ (...) не відбудеться автоматично" , це те, що воно буде автоматично, але це не відбувається, тому що базовий клас " __init__переосмислюється визначенням похідних класів__init__

Тоді ЧОМУ визначаючи похідний_клас ' __init__, оскільки він перекриває те, на що спрямований, коли хтось вдається до спадщини ??

Це тому, що потрібно визначити щось, що НЕ робиться в базовому класі ' __init__, і єдина можливість отримати це - поставити його виконання у функцію похідного класу' __init__.
Іншими словами, потрібно щось у базовому класі " __init__на додаток до того, що автоматично робиться в базовому класі", __init__якби цей останній не був відмінений.
НЕ навпаки.


Тоді проблема полягає в тому, що бажані вказівки, присутні в базовому класі ' __init__, більше не активуються в момент інстанції. Для того, щоб компенсувати цю інактивацію, потрібно щось особливе: явно викликати базовий клас ' __init__, щоб ЗБЕРІГАТИ , НЕ ДОДАТИ, ініціалізацію, виконану базовим класом' __init__. Саме так сказано в офіційному документі:

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

Ось і вся історія:

  • коли мета KEEP - ініціалізація, виконана базовим класом, це чисте успадкування, нічого особливого не потрібно, потрібно просто уникати визначення __init__ функції у похідному класі

  • коли мета - ЗАМІНА ініціалізації, виконаної базовим класом, __init__повинна бути визначена у похідному класі

  • коли метою є додавання процесів ADD до ініціалізації, що виконується базовим класом, __init__ повинен бути визначений похідний клас ' , що містить явний виклик базового класу__init__


Що я дивуюсь на посаді Анона - це не тільки те, що він висловлює протилежність теорії спадкування, але й те, що через них було порушено 5 хлопців, не обернувшись волоссям, і більше того, що за два роки в них ніхто не реагував нитка, цікаву тему якої треба читати порівняно часто.


1
Я був впевнений, що цю посаду буде схвалено. Я боюся, що я не буду мати багато пояснень з причин того, чому. Легше підкреслити, ніж проаналізувати текст, який видається незрозумілим. Я довго намагався зрозуміти пост Анона, перш ніж нарешті зрозумів, що це було сумлінно написано і не дуже авторитетно. Можливо, це можна трактувати як приблизно право для того, хто знає про спадщину; але я вважаю це заплутаним, коли читає хтось із нестабільними уявленнями про спадщину, предмет не такий чіткий, як скельна вода взагалі
eyquem

2
"Я був впевнений, що цю посаду буде схвалено ..." Головне питання полягає в тому, що ви трохи запізнюєтесь на вечірку, і більшість людей можуть не читати за першими кількома відповідями. Чудове пояснення до речі +1
Геррат

Чудова відповідь це.
Триларіон

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

5

Не існує жорсткого і швидкого правила. Документація до класу повинна вказувати, чи повинні підкласи викликати метод надкласу. Іноді ви хочете повністю замінити поведінку надкласових класів, а в інший час її доповнювати - тобто називати власний код до та / або після виклику надкласового класу.

Оновлення: та ж основна логіка застосовується до будь-якого виклику методу. Конструктори іноді потребують особливої ​​уваги (оскільки вони часто встановлюють стан, що визначає поведінку) та деструкторів, оскільки вони паралельні конструкторам (наприклад, при розподілі ресурсів, наприклад, підключення до бази даних). Але те саме може стосуватися, скажімо, і доrender() методу віджета.

Подальше оновлення: що таке OPP? Ви маєте на увазі ООП? Ні - підкласу часто потрібно щось знати про дизайн надкласу. Не внутрішні деталі впровадження - а базовий договір, який має суперклас зі своїми клієнтами (використовуючи класи). Це жодним чином не порушує принципів ООП. Ось чому protectedце дійсна концепція в ООП взагалі (хоча, звичайно, не в Python).


Ви сказали, що іноді хотіти б зателефонувати власним кодом перед викликом надкласового класу. Для цього потрібні знання про реалізацію батьківського класу, що порушило б ОПП.
Георг Шьолі

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.