Чому не використовуються автоматичні методи суперкласу __init__?


155

Чому дизайнери Python вирішили, що __init__()методи підкласів не автоматично називають __init__()методи своїх суперкласів, як у деяких інших мовах? Чи справді така пітонічна і рекомендована ідіома подобається наступному?

class Superclass(object):
    def __init__(self):
        print 'Do something'

class Subclass(Superclass):
    def __init__(self):
        super(Subclass, self).__init__()
        print 'Do something else'

2
Ви можете написати декоратор для успадкування __init__методу і навіть, можливо, автоматично шукати підкласи та прикрашати їх.
оса

2
@osa, звучить дійсно гарна ідея. Ви хочете трохи детальніше описати деталь декоратора?
Діаншенг

1
@osa так описати більше!
Чарлі Паркер

Відповіді:


163

Вирішальне відмінність між конструкторами Python __init__та іншими мовами полягає в тому, що він не є конструктором: це ініціалізатор (власне конструктор (якщо такий є, але, див. Пізніше ;-) є і знову працює зовсім по-іншому). У той час як будівництво всього суперкласу (і, без сумніву, робити це «раніше» ви продовжуєте будувати вниз), очевидно , частина кажучи , ви побудова примірника підкласу, тобто явно не той випадок для ініціалізації__init____new__, оскільки є багато випадків використання, коли ініціалізацію надкласових класів потрібно пропускати, змінювати, контролювати - якщо це взагалі відбувається "посередині" ініціалізації підкласу тощо.

По суті, делегування суперкласового ініціалізатора не є автоматичним в Python з тих самих причин, що таке делегування також не є автоматичним для будь-яких інших методів. інший метод ... просто для конструктора (і, якщо це застосовно, деструктора), який, як я вже згадував, не є тим, що є Python __init__. (Поведінка __new__також є досить своєрідною, хоча насправді безпосередньо не пов’язана з вашим питанням, оскільки __new__це такий своєрідний конструктор, що йому насправді не обов’язково щось конструювати - цілком може повернути існуючий екземпляр або навіть неінстанцію ... очевидно, що Python пропонує вам багатобільше контролю над механікою, ніж ви маєте на увазі "інші мови", що також включає відсутність автоматичного делегування в __new__собі! -).


7
Запропонований, оскільки для мене справжньою перевагою є здатність, яку ви згадуєте, викликати _ init () _ суперкласу в будь-якій точці ініціалізації підкласу (або зовсім не).
kindall

53
-1 для " __init__не конструктор ... власне конструктор ... є __new__". Як ви самі зазначаєте, __new__поводиться нічого не як конструктор з інших мов. __init__насправді дуже схожий (він називається під час створення нового об'єкта, після виділення цього об'єкта, для встановлення змінних членів на новий об’єкт), і майже завжди є місцем для реалізації функціоналу, який в інших мовах ви б помістили конструктор. Тож просто називайте це конструктором!
Бен

8
Я також думаю , що це досить абсурдне твердження: « будувати все суперклас ... очевидно частина кажучи ви побудова примірника підкласу, тобто явно не той випадок для ініціалізації ». У словах побудова / ініціалізація немає нічого, що робить це "очевидним" для кого-небудь. І __new__автоматично не викликає суперклас __new__. Отже, ваше твердження про те, що ключове відмінність полягає в тому, що побудова обов'язково передбачає побудову надкласових класів, тоді як ініціалізація не суперечить вашій заяві __new__про конструктор.
Бен

36
Насправді, з python docs for __init__at docs.python.org/reference/datamodel.html#basic-customization : "Як особливе обмеження для конструкторів , жодне значення не може бути повернене; це призведе до того, що TypeError буде підніматися під час виконання. "(наголос мій). Тож там, офіційно, __init__це конструктор.
Бен

3
У термінології Python / Java __init__називається конструктором. Цей конструктор - це функція ініціалізації, яка викликається після того, як об'єкт був повністю побудований та ініціалізований до стану за замовчуванням, включаючи його остаточний тип виконання. Це не еквівалентно C ++ конструкторам, які викликаються статично типованим виділеним об'єктом з невизначеним станом. Вони також відрізняються від __new__, тому ми справді маємо принаймні чотири різні функції розподілу / побудови / ініціалізації. Мови використовують змішану термінологію, і важливою частиною є поведінка, а не термінологія.
Елазар

36

Мені дещо соромно, коли люди папугують "Дзен Пітона", ніби це виправдання ні для чого. Це філософія дизайну; конкретні дизайнерські рішення завжди можна пояснити більш конкретними термінами - і вони повинні бути, інакше "Дзен Пітона" стає приводом для того, щоб робити що-небудь.

Причина проста: вам необов'язково конструювати похідний клас таким чином, який зовсім схожий на те, як ви створюєте базовий клас. У вас може бути більше параметрів, менше, вони можуть бути в іншому порядку або взагалі не пов'язані.

class myFile(object):
    def __init__(self, filename, mode):
        self.f = open(filename, mode)
class readFile(myFile):
    def __init__(self, filename):
        super(readFile, self).__init__(filename, "r")
class tempFile(myFile):
    def __init__(self, mode):
        super(tempFile, self).__init__("/tmp/file", mode)
class wordsFile(myFile):
    def __init__(self, language):
        super(wordsFile, self).__init__("/usr/share/dict/%s" % language, "r")

Це стосується всіх похідних методів, а не тільки __init__.


6
Чи пропонує цей приклад щось особливе? статичні мови можуть зробити це також
джинсові

18

Java та C ++ вимагають, щоб конструктор базового класу викликався через компонування пам'яті.

Якщо у вас є клас BaseClassз членом field1, і ви створюєте новий клас, SubClassякий додає член field2, то екземпляр SubClassмістить простір для field1і field2. BaseClassДля заповнення вам потрібен конструктор field1, якщо вам не потрібні всі класи успадковування для повторення BaseClassініціалізації в їхніх власних конструкторах. А якщо field1це приватне, то класи успадкування не можуть ініціалізуватися field1.

Python - це не Java або C ++. Усі екземпляри всіх визначених користувачем класів мають однакову "форму". Вони в основному просто словники, в які можна вставити атрибути. Перед тим, як було зроблено будь-яку ініціалізацію, всі екземпляри всіх визначених користувачем класів майже точно однакові ; вони просто місця для зберігання атрибутів, які ще не зберігаються.

Тому для підкласу Python є ідеальним не називати його конструктор базового класу. Він може просто додати самі атрибути, якщо цього захоче. Немає місця, відведеного для заданої кількості полів для кожного класу в ієрархії, і немає різниці між атрибутом, доданим кодом від BaseClassметоду, і атрибутом, доданим кодом з SubClassметоду.

Якщо, як прийнято, SubClassнасправді хочуть створити всі BaseClassінваріанти, перш ніж він продовжить робити власні налаштування, то так, ви можете просто зателефонувати BaseClass.__init__()(або використовувати super, але це складне і іноді має свої проблеми). Але не потрібно. І ви можете це робити до, або після, або з різними аргументами. Пекло, якби ти хотів, ти можеш зателефонувати BaseClass.__init__з іншого методу цілком, ніж __init__; можливо, у вас відбувається якась химерна лінива ініціалізація.

Python досягає цієї гнучкості шляхом простоти речей. Ви ініціалізуєте об'єкти, записавши __init__метод, який встановлює атрибути self. Це воно. Він поводиться точно як метод, тому що це саме метод. Немає інших дивних і неінтуїтивних правил щодо речей, які потрібно зробити спочатку, або речей, які автоматично відбудуться, якщо ви не зробите інших речей. Єдина мета, якій вона повинна служити, - це гак для виконання під час ініціалізації об'єкта для встановлення початкових значень атрибутів, і він робить саме це. Якщо ви хочете, щоб це робило щось інше, ви явно записуєте це у свій код.


2
Що стосується C ++, то це не має нічого спільного з "компонуванням пам'яті". Та ж модель ініціалізації, що й пропозиції Python, могла бути реалізована в C ++. Єдина причина, чому будівництво / руйнування є таким, яким вони є в C ++, - це проектне рішення забезпечити надійні та добре справні засоби управління ресурсами (RAII), які також можуть бути автоматично створені (маючи на увазі менше коду та людських помилок) компіляторами, оскільки правила для них (їх порядок дзвінків) чітко визначені. Не впевнений, але Java, швидше за все, просто дотримувалася цього підходу, щоб стати ще однією мовою, схожою на POLA C.
Олександр Шукаєв

10

"Явне краще, ніж неявне." Це те саме міркування, яке вказує на те, що ми повинні чітко писати «я».

Я думаю, врешті-решт, це користь - чи можете ви прочитати всі правила, які має Ява щодо виклику конструкторів суперкласів?


8
Я погоджуюся з вами здебільшого, але правило Java насправді досить просте: конструктор no-arg викликається, якщо ви спеціально не попросите іншого.
Лоранс Гонсальвес

1
@Laurence - Що відбувається, коли батьківський клас не визначає конструктор no-arg? Що відбувається, коли конструктор no-arg захищений або приватний?
Майк Аксіак

8
Точно те саме, що було б, якби ви спробували подзвонити це явно.
Лоранс Гонсальв

8

Часто підклас має додаткові параметри, які неможливо передати суперкласу.


7

Зараз у нас є досить довга сторінка, що описує порядок вирішення методу у випадку багаторазового успадкування: http://www.python.org/download/releases/2.3/mro/

Якщо конструктори викликали автоматично, вам знадобиться ще одна сторінка хоча б тієї ж довжини, що пояснює порядок того, що відбувається. Це було б пекло ...


Це була правильна відповідь. Python повинен був би визначити семантику аргументу, що передається між підкласом і надкласом. Шкода, що ця відповідь не оголошена. Може, якби це показало проблему на деяких прикладах?
Gary Weiss

5

Щоб уникнути плутанини, корисно знати, що ви можете викликати __init__()метод base_class, якщо у child_class немає __init__()класу.

Приклад:

class parent:
  def __init__(self, a=1, b=0):
    self.a = a
    self.b = b

class child(parent):
  def me(self):
    pass

p = child(5, 4)
q = child(7)
z= child()

print p.a # prints 5
print q.b # prints 0
print z.a # prints 1

Насправді MRO в python шукатиме __init__()в батьківському класі, коли не може знайти його в дитячому класі. Вам потрібно викликати конструктор батьківського класу безпосередньо, якщо у вас вже є __init__()метод у дитячому класі.

Наприклад, наступний код поверне помилку: батьківський клас: def init (self, a = 1, b = 0): self.a = a self.b = b

    class child(parent):
      def __init__(self):
        pass
      def me(self):
        pass

    p = child(5, 4) # Error: constructor gets one argument 3 is provided.
    q = child(7)  # Error: constructor gets one argument 2 is provided.

    z= child()
    print z.a # Error: No attribute named as a can be found.

3

Можливо, __init__це метод, який потребує переосмислення підкласу. Іноді підкласи потребують виконання батьківської функції перед тим, як додати специфічний для класу код, а інший раз їм потрібно встановити змінні екземпляра, перш ніж викликати батьківську функцію. Оскільки жоден спосіб Python не міг би знати, коли було б найдоцільніше викликати ці функції, він не повинен здогадуватися.

Якщо вони вас не влаштовують, вважайте, що __init__це просто ще одна функція. Якщо dostuffб замість них була відповідна функція, ви все ще хочете, щоб Python автоматично викликав відповідну функцію в батьківському класі?


2

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

Зрештою, тільки те, що клас B походить від класу A, не означає, що A.__init__()можна або слід називати тими ж аргументами, що і B.__init__(). зробити виклик явним засобом, який програміст може мати, наприклад, визначити B.__init__()з абсолютно різними параметрами, зробити деякі обчислення з цими даними, зателефонувати A.__init__()з аргументами у відповідних випадках для цього методу, а потім виконати деяку пост-обробку. такого роду гнучкості було б незручно домогтися, якщо A.__init__()вони будуть викликані B.__init__()неявно, перед B.__init__()виконанням або безпосередньо після неї.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.