Python super () не может вызвать унаследованный метод родителя - PullRequest
0 голосов
/ 03 июня 2018

У меня есть вопрос в Python, который кажется мне очень сложным, который сочетает в себе наследование, рекурсию и функцию super ().

Прежде всего, я использую Python 3, и у меня есть структураглубокого наследования.

В самом первом родительском классе я объявляю метод и хочу, чтобы этот метод вызывался из каждого дочернего класса в иерархии, но с разными входными данными для каждого из них.

Использование этой структуры мне кажется очень питонным, и это действительно спасает меня от большого количества повторений кода.

Упрощенный пример моего кода показан ниже:

class ClassA(object):
    def __init__(self):
        self.attr = 'a'


    @classmethod
    def _classname(cls):
        return cls.__name__


    def method1(self):
        if self._classname() != 'ClassA': #don't call it for the very first parent class
            super().method1()
        print(self.attr)

class ClassB(ClassA):
    def __init__(self):
        self.attr = 'b'


class ClassC(ClassB):
    def __init__(self):
        self.attr = 'c'


inst1 = ClassC()
inst1.method1()

Я ожидаю, что этот код напечатает

'a'
'b'
'c'

Вместо этого возникает ошибка атрибута:

super().method1()
AttributeError: 'super' object has no attribute 'method1'

Я знаю, что это сложная проблема, но я попытался разделить ее,Я пытался удалить рекурсивную часть, но я не поправляюсь.

Основываясь на различных попытках, которые я предпринял, я считаю, что я очень близок к причине проблемы, и мне кажется,как проблема синтаксиса или что-то такое простое.

Спасибо !!

Ответы [ 2 ]

0 голосов
/ 03 июня 2018

Боюсь, вы создали неверную ментальную модель того, как связаны экземпляры и классы 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

напечатано.

0 голосов
/ 03 июня 2018

Это потому, что вы пытаетесь получить доступ к Method1 () в Object_class

def method1(self):
    # this will print the class name you're calling from, if you are confused.
    print(self._classname()) 
    print(self.attr)

Вы написали if-else, который удовлетворяет, когда вы создаете экземпляр класса C и получаете доступ к method1 ().

Чтобы напечатать a, b, c, вам потребуется переопределить метод в каждом классе ... и вызвать super из них.Тем не менее, это все еще будет иметь проблемы.Поскольку атрибут self содержит экземпляр класса C, а не класса B или A. Таким образом, они не могут получить доступ к атрибуту, который вы инициализируете в функции init .

Итоговый код выглядит следующим образом

class ClassA(object):
    def __init__(self):
        self.attr = 'a'


    @classmethod
    def _classname(cls):
        return cls.__name__


    def method1(self):
        print(self._classname())
        #don't call it for the very first parent class
        #super().method1()
        print(self.attr)

class ClassB(ClassA):
    def __init__(self):
        self.attr = 'b'

    def method1(self):
        print(self._classname())
        print(self.attr)
        super().method1()


class ClassC(ClassB):
    def __init__(self):
       self.attr = 'c'

    def method1(self):
        print(self._classname())
        print(self.attr)
        super().method1()


inst1 = ClassC()
inst1.method1()
...