Почему методы суперкласса __init__ не вызываются автоматически? - PullRequest
140 голосов
/ 24 сентября 2010

Почему разработчики 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'

Ответы [ 9 ]

155 голосов
/ 24 сентября 2010

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

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

36 голосов
/ 24 сентября 2010

Я несколько смущен, когда люди попугаи "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__.

16 голосов
/ 24 октября 2011

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

Если у вас есть класс BaseClass с членом field1, и вы создаетеновый класс SubClass, который добавляет член field2, затем экземпляр SubClass содержит пространство для field1 и field2.Вам нужен конструктор BaseClass для заполнения field1, если только вам не требуется, чтобы все наследующие классы повторяли инициализацию BaseClass в их собственных конструкторах.И если field1 является закрытым, то наследуемые классы не могут инициализировать field1.

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

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

Если, как обычно, SubClass действительно хочет настроить все инварианты BaseClass до того, как он выполнит свою собственную настройку, тогда да, вы можете просто позвонить BaseClass.__init__() (или использовать * 1032).*, но это сложно и иногда имеет свои проблемы).Но ты не обязан.И вы можете сделать это до, или после, или с другими аргументами.Черт, если бы вы хотели, вы могли бы вызвать BaseClass.__init__ из другого метода полностью, чем __init__;может быть, у вас происходит какая-то странная ленивая инициализация.

Python достигает этой гибкости, делая вещи простыми.Вы инициализируете объекты, написав метод __init__, который устанавливает атрибуты на self.Вот и все.Он ведет себя точно так же, как метод, потому что это точно метод.Нет других странных и не интуитивно понятных правил о том, что нужно делать в первую очередь, или о вещах, которые автоматически произойдут, если вы не будете делать другие вещи.Единственная цель, которой он должен служить, - это ловушка для выполнения во время инициализации объекта для установки начальных значений атрибута, и он делает именно это.Если вы хотите, чтобы это делало что-то еще, вы явно пишете это в своем коде.

10 голосов
/ 24 сентября 2010

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

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

8 голосов
/ 24 сентября 2010

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

7 голосов
/ 24 сентября 2010

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

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

4 голосов
/ 26 октября 2017

Чтобы избежать путаницы, полезно знать, что вы можете вызвать метод base_class __init__(), если 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 голосов
/ 24 сентября 2010

Может быть __init__ - это метод, который подкласс должен переопределить.Иногда подклассам нужна функция родителя для запуска, прежде чем они добавят специфичный для класса код, а в других случаях им нужно установить переменные экземпляра перед вызовом функции родителя.Поскольку Python никак не может знать, когда это будет наиболее уместно для вызова этих функций, он не должен догадываться.

Если они вас не побуждают, учтите, что __init__ - это просто другая функция.Если бы рассматриваемая функция была dostuff, вы все равно хотели бы, чтобы Python автоматически вызывал соответствующую функцию в родительском классе?

2 голосов
/ 24 сентября 2010

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

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

...