Боюсь, вы создали неверную ментальную модель того, как связаны экземпляры и классы Python.Классы предоставляют только серию атрибутов для «наследования» экземпляров, а не отдельные пространства имен для атрибутов экземпляров, в которых можно жить. Когда вы просматриваете атрибут в экземпляре, а атрибут не существует в самом экземпляре, выполняется поискклассы, которые поддерживают экземпляр, причем «ближайший» класс с этим атрибутом побеждает другие.super()
просто позволяет вам получить атрибуты с тем же именем, но определенными для следующего класса в том же многоуровневом пространстве поиска.
Чтобы super()
работал правильно, Python записывает, какой класс method1
функция была определена для .Здесь это ClassA
, и super()
будет только найти атрибуты в родительских классах ClassA
.В вашем примере ClassC
и ClassB
уже были найдены, и у них не было атрибута method1
, поэтому используется ClassA.method1
, но в остальных слоях больше нет атрибута method1
которые ищут (остается только object
, а object.method1
нет).
Вам не нужно использовать super()
, когда подклассы не переопределяют метод, и вы не можете делать то, что хотитес super()
в любом случае.Обратите внимание, что подклассы ClassB
и ClassC
вообще не получают копию метода, отсутствует прямой атрибут ClassC.method1
, который должен учитывать существование ClassB.method1
и т. Д. Опять же, что происходит при поискеАтрибуты в экземпляре таковы, что все объекты класса в иерархии наследования экземпляра проверяются на наличие этого атрибута в определенном порядке.
Посмотрите на ваши подклассы:
>>> inst1
<__main__.ClassC object at 0x109a9dfd0>
>>> type(inst1)
<class '__main__.ClassC'>
>>> type(inst1).__mro__
(<class '__main__.ClassC'>, <class '__main__.ClassB'>, <class '__main__.ClassA'>, <class 'object'>)
Атрибут __mro__
дает вам порядок разрешения метода вашего ClassC
объекта класса;именно этот порядок ищет атрибуты и использует super()
для дальнейшего поиска атрибутов.Чтобы найти inst1.method
, Python пропустит каждый из объектов в type(inst1).__mro__
и вернет первое попадание, поэтому ClassA.method1
.
В вашем примере вы использовали super()
в ClassA.method1()
определение.Python приложил некоторую информацию к этому функциональному объекту, чтобы помочь дальнейшему поиску атрибутов:
>>> ClassA.method1.__closure__
(<cell at 0x109a3fee8: type object at 0x7fd7f5cd5058>,)
>>> ClassA.method1.__closure__[0].cell_contents
<class '__main__.ClassA'>
>>> ClassA.method1.__closure__[0].cell_contents is ClassA
True
Когда вы вызываете super()
, замыкание, которое я показываю выше, используется для запуска поиска по последовательности type(self).__mro__
, начиная с следующего объекта, который указан в закрытии .Неважно, что здесь есть подклассы, поэтому даже для вашего inst1
объекта все пропускается, проверяется только object
:
>>> type(inst1).__mro__.index(ClassA) # where is ClassA in the sequence?
2
>>> type(inst1).__mro__[2 + 1:] # `super().method1` will only consider these objects, *past* ClassA
(<class 'object'>,)
Ни в коем случае не являются ClassB
или ClassC
участвует здесь больше.MRO зависит от иерархии классов текущего экземпляра, и вы можете внести радикальные изменения, когда начнете использовать множественное наследование .Добавление дополнительных классов в иерархию может изменить MRO достаточно, чтобы вставить что-то между ClassA
и object
:
>>> class Mixin(object):
... def method1(self):
... print("I am Mixin.method1!")
...
>>> class ClassD(ClassA, Mixin): pass
...
>>> ClassD.__mro__
(<class '__main__.ClassD'>, <class '__main__.ClassA'>, <class '__main__.Mixin'>, <class 'object'>)
>>> ClassD.__mro__[ClassD.__mro__.index(ClassA) + 1:]
(<class '__main__.Mixin'>, <class 'object'>)
ClassD
наследуется от ClassA
и от Mixin
.Mixin
наследуется от object
тоже .Python следует некоторым сложным правилам , чтобы поместить все классы в иерархии в логический линейный порядок, и Mixin
заканчивается между ClassA
и object
, потому что он наследует от второго, а не от первого.
Поскольку Mixin
вводится в MRO после ClassA
, вызов Class().method1()
изменяет поведение super().method1()
, и внезапный вызов этого метода будет делать что-то другое:
>>> ClassD().method1()
I am Mixin.method1!
a
Помните, что это помогает видеть классы как многоуровневое пространство поиска для атрибутов в экземплярах!instance.attribute
ищется вдоль классов, если атрибут не существует в самом экземпляре.super()
просто позволяет вам искать тот же атрибут по оставшейся части этого пространства поиска.
Это позволяет вам повторно использовать реализации метода при реализации метода с тем же именем в подклассе.В этом весь смысл super()
!
Есть другие проблемы с вашим кодом.
При поиске методов они привязаны к объекту, на котором они были просмотрены .instance.method
связывает метод с instance
, поэтому при вызове instance.method()
Python знает, что передать в метод как self
.Для classmethod
объектов self
заменяется на type(self)
, если только вы не сделали ClassObject.attribute
, для которого используется ClassObject
.
Таким образом, ваш _classname
метод будет всегда производит ClassC
для inst1
, поскольку переданный объект cls
соответствует текущему экземпляру.super()
не меняет того, к какому классу classmethod
привязаны при доступе к экземпляру!Это будет всегда быть type(self)
.
Вы также забыли вызвать super()
в методах ClassB
и ClassC
__init__
, поэтомудля inst1
фактически используется только ClassC.__init__
.Реализации ClassB.__init__
и ClassC.__init__
никогда не вызываются.Вы должны добавить вызов к super().__init__()
в обоих случаях, чтобы в этот момент было три назначения self.attr = ...
в одном и том же экземпляре, и останется только то, которое выполняется последним.Не существует отдельных self
для каждого из классов, составляющих код для экземпляра, поэтому нет отдельных атрибутов self.attr
с разными значениями.
Опять же, это потому, что вызывается inst1.__init__()
,__init__
связан с inst
для аргумента self
, и даже если вы использовали super().__init__()
, self
, который передается, остается inst1
.
ЧтоВы хотите достичь чего-то совершенно отличного от поиска атрибутов во всех классах.Печать всех имен классов может быть сделана с циклом над __mro__
вместо:
class ClassA(object):
def method2(self):
this_class = __class__ # this uses the same closure as super()!
for cls in type(self).__mro__:
print(cls.__name__)
if cls is this_class:
break
class ClassB(ClassA): pass
class ClassC(ClassB): pass
Это затем выдает:
>>> inst1 = ClassC()
>>> inst1.method2()
ClassC
ClassB
ClassA
Если вам нужно напечатать 'c'
, 'b'
, 'a'
вы можете добавить дополнительные атрибуты для каждого класса:
class ClassA(object):
_class_attr = 'a'
def method2(self):
this_class = __class__ # this uses the same closure as super()!
for cls in type(self).__mro__:
if hasattr(cls, '_class_attr'):
print(cls._class_attr)
class ClassB(ClassA):
_class_attr = 'b'
class ClassC(ClassB):
_class_attr = 'c'
и вы получите
c
b
a
напечатано.