Почему разработчики 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'
__init__
метода и даже, возможно, автоматически искать подклассы и декорировать их.Ответы:
Ключевое различие между Python
__init__
и другими языковыми конструкторами заключается в том, что__init__
это не конструктор: это инициализатор (фактический конструктор (если есть, но, смотрите позже ;-)__new__
снова и снова работает совершенно иначе). В то время как строительство всего суперкласса (и, без сомнения, делать это «раньше» вы продолжаете строить вниз), очевидно , часть говоря , вы построение экземпляра подкласса, то есть явно не тот случай для инициализациипоскольку существует много вариантов использования, в которых инициализация суперклассов должна быть пропущена, изменена, контролируема - происходит, если вообще происходит, «в середине» инициализации подкласса и так далее.По сути, делегирование инициализатора суперкласса не является автоматическим в Python по тем же причинам, по которым такое делегирование также не является автоматическим для любых других методов - и обратите внимание, что эти «другие языки» не делают автоматическое делегирование суперкласса ни для каких других методов. либо другой метод ... просто для конструктора (и, если применимо, деструктора), который, как я уже говорил, не тот , который есть в Python
__init__
. (Поведение__new__
также довольно своеобразно, хотя на самом деле не имеет прямого отношения к вашему вопросу, поскольку__new__
является настолько своеобразным конструктором, что на самом деле ему не обязательно создавать что-либо - он вполне может вернуть существующий экземпляр или даже неэкземпляр ... ясно, что Python предлагает вам многобольше контроля над механикой, чем «другие языки», которые вы имеете в виду, что также включает в себя отсутствие автоматического делегирования__new__
! -).источник
__init__
не является конструктором ... фактический конструктор ... является__new__
". Как вы сами заметили,__new__
ведет себя не так, как конструктор из других языков.__init__
на самом деле очень похож (он вызывается во время создания нового объекта, после того , как этот объект выделен, чтобы установить переменные-члены для нового объекта), и почти всегда является местом для реализации функциональности, которую вы бы добавили в других языках конструктор. Так что просто назовите это конструктором!__new__
автоматически не вызывает суперкласс__new__
. Таким образом, вы утверждаете, что ключевое различие заключается в том, что конструирование обязательно включает в себя конструирование суперклассов, в то время как инициализация не противоречит вашему утверждению, которое__new__
является конструктором.__init__
at docs.python.org/reference/datamodel.html#basic-customization : «Как специальное ограничение для конструкторов , значение не может быть возвращено; это приведет к возникновению ошибки TypeError во время выполнения "(выделение мое). Так что, это официально,__init__
это конструктор.__init__
называется конструктором. Этот конструктор является функцией инициализации, вызываемой после того, как объект был полностью сконструирован и инициализирован в состояние по умолчанию, включая его конечный тип времени выполнения. Он не эквивалентен конструкторам C ++, которые вызываются для статически типизированного выделенного объекта с неопределенным состоянием. Они также отличаются от других__new__
, поэтому у нас действительно есть как минимум четыре различных типа функций выделения / построения / инициализации. Языки используют смешанную терминологию, и важной частью является поведение, а не терминология.Я несколько смущен, когда люди попугают «Zen of Python», как будто это оправдание для чего-либо. Это философия дизайна; конкретные конструктивные решения всегда можно объяснить более конкретными терминами - и они должны быть, иначе «Zen of Python» станет оправданием для любых действий.
Причина проста: вы не обязательно создаете производный класс способом, аналогичным тому, как вы строите базовый класс. У вас может быть больше параметров, меньше, они могут быть в другом порядке или вообще не связаны.
Это относится ко всем производным методам, а не только
__init__
.источник
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
. Вот и все. Он ведет себя точно так же, как метод, потому что это точно метод. Нет других странных и не интуитивно понятных правил о том, что нужно делать в первую очередь, или о вещах, которые автоматически произойдут, если вы не будете делать другие вещи. Единственная цель, которой он должен служить, - это ловушка для выполнения во время инициализации объекта для установки начальных значений атрибута, и он делает именно это. Если вы хотите, чтобы это делало что-то другое, вы явно пишете это в своем коде.источник
«Явное лучше, чем неявное». Это то же самое рассуждение, которое указывает, что мы должны явно написать «я».
Я думаю, в конце концов, это преимущество - можете ли вы перечислить все правила, которые есть в Java относительно вызова конструкторов суперклассов?
источник
Часто у подкласса есть дополнительные параметры, которые нельзя передать суперклассу.
источник
Прямо сейчас у нас есть довольно длинная страница, описывающая порядок разрешения методов в случае множественного наследования: http://www.python.org/download/releases/2.3/mro/
Если бы конструкторы вызывались автоматически, вам понадобилась бы другая страница, по крайней мере, такой же длины, объясняющая порядок этого. Это был бы ад ...
источник
Чтобы избежать путаницы, полезно знать, что вы можете вызвать
__init__()
метод base_class, если child_class не имеет__init__()
класса.Пример:
Фактически MRO в python будет искать
__init__()
в родительском классе, когда не может найти его в дочернем классе. Вам нужно напрямую вызывать конструктор родительского класса, если у вас уже есть__init__()
метод в дочернем классе.Например, следующий код вернет ошибку: class parent: def init (self, a = 1, b = 0): self.a = a self.b = b
источник
Возможно
__init__
это метод, который подкласс должен переопределить. Иногда подклассам нужна функция родителя для запуска, прежде чем они добавят специфичный для класса код, а в других случаях им нужно установить переменные экземпляра перед вызовом функции родителя. Поскольку Python никак не может знать, когда будет наиболее уместно вызывать эти функции, он не должен догадываться.Если тебя это не поколебает, подумай, что
__init__
это просто еще одна функция. Если бы рассматриваемая функция былаdostuff
вместо этого, вы бы хотели, чтобы Python автоматически вызывал соответствующую функцию в родительском классе?источник
я полагаю, что одно очень важное соображение здесь заключается в том, что при автоматическом вызове
super.__init__()
вы по замыслу запрещаете, когда вызывается этот метод инициализации и с какими аргументами. Отказ от автоматического вызова и требование, чтобы программист явно делал этот вызов, влечет за собой большую гибкость.В конце концов, только то, что класс B является производным от класса A, не означает, что он
A.__init__()
может или должен вызываться с теми же аргументами, что иB.__init__()
. явный вызов означает, что программист может иметь, например, определениеB.__init__()
с совершенно разными параметрами, выполнить некоторые вычисления с этими данными, вызвать ихA.__init__()
с аргументами, подходящими для этого метода, а затем выполнить некоторую постобработку. такого рода гибкость было бы неудобно достигать, еслиA.__init__()
вызывать ееB.__init__()
неявно, либо перед выполнением,B.__init__()
либо сразу после него.источник