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

Почему дизайнеры 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'

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

Чтобы избежать путаницы, полезно знать, что вы можете вызвать метод 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__() в родительском классе, если не сможет найти его в классе children. Вы должны вызвать конструктор родительского класса напрямую, если у вас уже есть метод __init__() в классе children.

Например, следующий код вернет ошибку: 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.