Как я понимаю из книги, если Sub наследует Super, то нет необходимости вызывать метод суперкласса (Super
) __init__()
.
Это вводит в заблуждение. Это правда, что вы не обязаны вызывать метод __init__
суперкласса - но если вы этого не сделаете, то, что он делает в __init__
, никогда не происходит. И для обычных классов все это должно быть сделано. иногда полезно, обычно, когда класс не предназначен для наследования, например:
class Rot13Reader:
def __init__(self, filename):
self.file = open(filename):
def close(self):
self.file.close()
def dostuff(self):
line = next(file)
return codecs.encode(line, 'rot13')
Представьте, что вам нужно все поведение этого класса, но со строкой, а не с файлом. Единственный способ сделать это - пропустить open
:
class LocalRot13Reader(Rot13Reader):
def __init__(self, s):
# don't call super().__init__, because we don't have a filename to open
# instead, set up self.file with something else
self.file = io.StringIO(s)
Здесь мы хотели избежать назначения self.file
в суперклассе. В вашем случае, как и почти во всех классах, которые вы когда-либо будете писать, вы не не хотите избегать назначения self.name
в суперклассе. Вот почему, хотя Python позволяет вам не вызывать __init__
суперкласса, вы почти всегда вызываете его.
Обратите внимание, что здесь нет ничего особенного в __init__
. Например, мы можем переопределить dostuff
для вызова версии базового класса, а затем выполнить дополнительные действия:
def dostuff(self):
result = super().dostuff()
return result.upper()
… или мы можем переопределить close
и намеренно не вызывать базовый класс:
def close(self):
# do nothing, including no super, because we borrowed our file
Единственное отличие состоит в том, что веские причины избегать вызова базового класса обычно встречаются в обычных методах гораздо чаще, чем в __init__
.
Вопрос: Зачем мне звонить Super's __init__
, используя Super.__init__(self, name)
ИЛИ super(Sub, self).__init__(name)
вместо прямого звонка Super(name)
?
Потому что они делают очень разные вещи.
Super(name)
создает новый экземпляр Super
, вызывает __init__(name)
и возвращает его вам. И тогда вы игнорируете это значение.
В частности, Super.__init__
вызывается один раз в любом случае, но self
, с которым он вызывается, - это новый экземпляр Super
, который вы просто собираетесь выбросить, в случае Super(name)
в то время как это ваше self
в super(Sub, self).__init__(name)
случае.
Итак, в первом случае он устанавливает атрибут name
для какого-либо другого объекта, который выбрасывается, и никто никогда не устанавливает его для вашего объекта, поэтому self.name
позже поднимает AttributeError
.
Это может помочь вам понять это, если вы добавите что-то в оба метода __init__
класса, чтобы показать, какой экземпляр задействован:
class Super:
def __init__(self,name):
print(f"Inside Super __init__ for {self}")
self.name=name
print("Name is:",name)
class Sub(Super):
def __init__(self,name):
print(f"Inside Sub __init__ for {self}")
# line you want to experiment with goes here.
Если последняя строка super().__init__(name)
, super(Sub, self).__init__name)
или Super.__init__(self, name)
, вы увидите что-то вроде этого:
Inside Sub __init__ for <__main__.Sub object at 0x10f7a9e80>
Inside Super __init__ for <__main__.Sub object at 0x10f7a9e80>
Обратите внимание, что это один и тот же объект, Sub
по адресу 0x10f7a9e80, в обоих случаях.
… но если последняя строка Super(name)
:
Inside Sub __init__ for <__main__.Sub object at 0x10f7a9ea0>
Inside Super __init__ for <__main__.Super object at 0x10f7a9ec0>
Теперь у нас есть два разных объекта с разными адресами 0x10f7a9ea0 и 0x10f7a9ec0 и с разными типами.
Если вам интересно, как выглядит магия под одеялом, Super(name)
делает что-то вроде этого (немного упрощает и пропускает некоторые шаги 1 ):
_newobj = Super.__new__(Super)
if isinstance(_newobj, Super):
Super.__init__(_newobj, name)
… пока super(Sub, self).__init__(name)
делает что-то вроде этого:
_basecls = magically_find_next_class_in_mro(Sub)
_basecls.__init__(self, name)
В качестве примечания: если книга говорит вам использовать super(Sub, self).__init__(name)
или Super.__init__(self, name)
, возможно, это устаревшая книга, написанная для Python 2.
В Python 3 вы просто делаете это:
super().__init__(name)
: Вызывает правильный следующий суперкласс по порядку разрешения метода. Вы почти всегда этого хотите.
super(Sub, self).__init__(name)
: Вызывает правильный следующий суперкласс - если вы не ошиблись и не ошиблись Sub
. Это необходимо только в том случае, если вы пишете код с двумя версиями, который должен работать в 2.7 и 3.x.
Super.__init__(self, name)
: Вызывает Super
, будь то правильный следующий суперкласс или нет. Это необходимо только в том случае, если порядок разрешения метода неправильный, и вам нужно его обойти. 2
Если вы хотите понять больше, все это есть в документации, но это может быть немного утомительно:
оригинальное введение в super
, __new__
и все связанные с ним функции очень помогло мне понять все это. Я не уверен, будет ли это полезным для кого-то, кто не знаком с уже понимающими классы Python в старом стиле, но он довольно хорошо написан, и Гвидо (очевидно) знает, о чем говорит, так что, возможно, стоит прочитать .
1. Самый большой обман в этом объяснении состоит в том, что super
на самом деле возвращает прокси-объект, который действует как _baseclass
, связанный с self
, так же, как связаны методы, которые могут использоваться для привязки методов, таких как __init__
. Это полезное / интересное знание, если вы знаете, как работают методы, но, возможно, просто лишняя путаница, если вы этого не сделаете.
2. ... или если вы работаете с классами старого стиля, которые не поддерживают super
(или правильный порядок разрешения методов). Это никогда не происходит в Python 3, который не имеет классов в старом стиле. Но, к сожалению, вы увидите это во многих примерах tkinter, потому что лучшим учебником по-прежнему является Effbot, написанный для Python 2.3, когда Tkinter был классами старого стиля и никогда не обновлялся.