Почему методы суперкласса __init__ не вызываются автоматически?

155

Почему разработчики Python решили, что __init__()методы подклассов не вызывают автоматически __init__()методы своих суперклассов, как в некоторых других языках? Действительно ли Pythonic и рекомендуемая идиома похожи на следующее?

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

class Subclass(Superclass):
    def __init__(self):
        super(Subclass, self).__init__()
        print 'Do something else'
jrdioko
источник
2
Вы можете написать декоратор для наследования __init__метода и даже, возможно, автоматически искать подклассы и декорировать их.
Ос
2
@ Оса, звучит действительно хорошая идея. Вы хотели бы описать немного больше в части декоратора?
Diansheng
1
@ О да, опиши еще!
Чарли Паркер

Ответы:

163

Ключевое различие между Python __init__и другими языковыми конструкторами заключается в том, что __init__это не конструктор: это инициализатор (фактический конструктор (если есть, но, смотрите позже ;-) __new__снова и снова работает совершенно иначе). В то время как строительство всего суперкласса (и, без сомнения, делать это «раньше» вы продолжаете строить вниз), очевидно , часть говоря , вы построение экземпляра подкласса, то есть явно не тот случай для инициализациипоскольку существует много вариантов использования, в которых инициализация суперклассов должна быть пропущена, изменена, контролируема - происходит, если вообще происходит, «в середине» инициализации подкласса и так далее.

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

Алекс Мартелли
источник
7
Принято решение, потому что для меня реальным преимуществом является упомянутое вами умение вызывать _ init () _ суперкласса в любой момент инициализации подкласса (или не делать это вообще).
любезно
53
-1 для " __init__не является конструктором ... фактический конструктор ... является __new__". Как вы сами заметили, __new__ведет себя не так, как конструктор из других языков. __init__на самом деле очень похож (он вызывается во время создания нового объекта, после того , как этот объект выделен, чтобы установить переменные-члены для нового объекта), и почти всегда является местом для реализации функциональности, которую вы бы добавили в других языках конструктор. Так что просто назовите это конструктором!
Бен
8
Я также думаю , что это довольно абсурдное утверждение: « строить все суперкласс ... очевидно часть говоря вы построение экземпляра подкласса, то есть явно не тот случай для инициализации ». В конструкции / инициализации слов нет ничего, что делает это «очевидным» для всех. И __new__автоматически не вызывает суперкласс __new__. Таким образом, вы утверждаете, что ключевое различие заключается в том, что конструирование обязательно включает в себя конструирование суперклассов, в то время как инициализация не противоречит вашему утверждению, которое __new__является конструктором.
Бен
36
Фактически, из документации по python для __init__at docs.python.org/reference/datamodel.html#basic-customization : «Как специальное ограничение для конструкторов , значение не может быть возвращено; это приведет к возникновению ошибки TypeError во время выполнения "(выделение мое). Так что, это официально, __init__это конструктор.
Бен
3
В терминологии Python / Java __init__называется конструктором. Этот конструктор является функцией инициализации, вызываемой после того, как объект был полностью сконструирован и инициализирован в состояние по умолчанию, включая его конечный тип времени выполнения. Он не эквивалентен конструкторам C ++, которые вызываются для статически типизированного выделенного объекта с неопределенным состоянием. Они также отличаются от других __new__, поэтому у нас действительно есть как минимум четыре различных типа функций выделения / построения / инициализации. Языки используют смешанную терминологию, и важной частью является поведение, а не терминология.
Элазар
36

Я несколько смущен, когда люди попугают «Zen of Python», как будто это оправдание для чего-либо. Это философия дизайна; конкретные конструктивные решения всегда можно объяснить более конкретными терминами - и они должны быть, иначе «Zen of Python» станет оправданием для любых действий.

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

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это private, то наследующие классы не могут инициализироваться 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

«Явное лучше, чем неявное». Это то же самое рассуждение, которое указывает, что мы должны явно написать «я».

Я думаю, в конце концов, это преимущество - можете ли вы перечислить все правила, которые есть в Java относительно вызова конструкторов суперклассов?

Майк Аксиак
источник
8
Я согласен с вами по большей части, но правило Java на самом деле довольно простое: конструктор no-arg вызывается, если вы специально не запросите другой.
Лоуренс Гонсалвес
1
@Laurence - Что происходит, когда родительский класс не определяет конструктор без аргументов? Что происходит, когда конструктор без аргументов защищен или закрыт?
Майк Аксиак
8
Точно то же самое, что произошло бы, если бы вы попытались вызвать это явно.
Лоуренс Гонсалвес
8

Часто у подкласса есть дополнительные параметры, которые нельзя передать суперклассу.

Джон Ла Рой
источник
7

Прямо сейчас у нас есть довольно длинная страница, описывающая порядок разрешения методов в случае множественного наследования: http://www.python.org/download/releases/2.3/mro/

Если бы конструкторы вызывались автоматически, вам понадобилась бы другая страница, по крайней мере, такой же длины, объясняющая порядок этого. Это был бы ад ...

viraptor
источник
Это был правильный ответ. Python должен был бы определить семантику передачи аргументов между подклассом и суперклассом. Жаль, что этот ответ не проголосовал. Может быть, если это показало проблему с некоторыми примерами?
Гари Вайс,
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__()метод в дочернем классе.

Например, следующий код вернет ошибку: class parent: 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.
Keyvanrm
источник
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__()либо сразу после него.

течь
источник