Подтвердить что ты не робот

Python, переопределение метода унаследованного класса

У меня есть два класса: Field и Background. Они выглядят примерно так:

class Field( object ):
    def __init__( self, a, b ):
        self.a = a
        self.b = b
        self.field = self.buildField()

    def buildField( self ):
        field = [0,0,0]
        return field

class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__( a, b )
        self.field = self.buildField( c )

    def buildField( self, c ):
        field = [c]
        return field

a, b, c = 0, 1, 2
background = Background( a, b, c )

Эта ошибка указывает на поле buildField():

"TypeError: buildField() takes exactly 2 arguments (1 given)."

Я ожидал, что сначала будет вызываться Background init(). Чтобы передать "a, b" в поля init(), поле, чтобы назначить a и b, затем назначить список с тремя 0 в нем в поле. Затем для Background init() продолжить, чтобы затем вызвать свой собственный buildField() и переопределить self.field со списком, содержащим c.

Кажется, я не полностью понимаю super(), однако мне не удалось найти решение моей проблемы после просмотра похожих проблем наследования в Интернете и здесь.

Я ожидал такого поведения, как С++, где класс может переопределить метод, который был унаследован. Как я могу достичь этого или чего-то подобного.

Большинство проблем, которые я нашел, связаны с тем, что люди используют двойные подчеркивания. Мой опыт с наследованием с супер заключается в использовании унаследованного класса init(), чтобы просто передать разные переменные суперклассу. Ничего не связано с перезаписью чего-либо.

4b9b3361

Ответ 1

Исходя из перспективы C++, здесь может быть два неправильных представления.

Во-первых, метод с тем же именем и другой подписью не перегружает его, как в C++. Если один из ваших фоновых объектов попытается вызвать buildField без аргументов, исходная версия из Field не будет вызвана - она полностью скрыта.

Вторая проблема заключается в том, что если метод, определенный в суперклассе, вызывает buildField, будет вызвана версия подкласса. В python все методы связаны динамически, как метод C++ virtual.

Поле __init__ ожидало иметь дело с объектом, у которого был метод buildField без аргументов. Вы использовали метод с объектом, у которого метод buildField принимает один аргумент.

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

Ответ 2

Я ожидал, что будет вызван Background init(). Передача "a, b" полям init(), Поле для назначения a и b

До сих пор так хорошо.

затем назначить список с тремя 0 в этом поле.

Ах. Здесь мы получаем ошибку.

    self.field = self.buildField()

Даже если эта строка встречается внутри Field.__init__, self является экземпляром Background. поэтому self.buildField находит метод Background buildField, а не Field.

Так как Background.buildField ожидает 2 аргумента вместо 1,

self.field = self.buildField()

вызывает ошибку.


Итак, как мы можем сказать Python вызывать метод Field buildField вместо Background?

Цель name mangling (присвоение имени двойным символам подчеркивания) заключается в решении этой точной проблемы.

class Field(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.field = self.__buildField()

    def __buildField(self):
        field = [0,0,0]
        return field

class Background(Field):
    def __init__(self, a, b, c):
        super(Background, self).__init__(a, b)
        self.field = self.__buildField(c)

    def __buildField(self, c):
        field = [c]
        return field

a, b, c = 0, 1, 2
background = Background(a, b, c)

Имя метода __buildField "искажено" до _Field__buildField внутри Field, поэтому внутри Field.__init__,

    self.field = self.__buildField()

вызывает self._Field__buildField(), который является Field __buildField. Аналогичным образом,

    self.field = self.__buildField(c)

внутри Background.__init__ вызывает метод Background __buildField.

Ответ 3

Я ожидал, что Background init() будет называться

На самом деле Background init() получает вызов..

Но взгляните на свой фоновый класс.

class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__( a, b )
        self.field = self.buildField( c )

Итак, первый оператор __init__ вызывает метод super class(Field) init.. и передает аргумент self в качестве аргумента.. Теперь это self на самом деле является ссылкой Background class..

Теперь в вашем полевом классе: -

class Field( object ):
    def __init__( self, a, b ):

        print self.__class__  // Prints `<class '__main__.Background'>`
        self.a = a
        self.b = b
        self.field = self.buildField()

Ваш метод buildField() на самом деле вызывает тот из класса Background. Это потому, что self здесь является экземпляром класса Background (попробуйте напечатать self.__class__ в вашем методе __init__ Field class).. Когда вы передали его при вызове метода __init__, из Background class..

Вот почему вы получаете ошибку.

Ошибка "TypeError: buildField() принимает ровно 2 аргумента (1 дано).

Поскольку вы не передаете какое-либо значение. Итак, только переданное значение является неявным self.

Ответ 4

super(Background, self).__init__( a, b ) будет вызывать:

def __init__( self, a, b ):
    self.a = a
    self.b = b
    self.field = self.buildField()

в Field. Однако self здесь ссылается на экземпляр background, а self.buildField() на самом деле вызывает buildField() background, поэтому вы получаете эту ошибку.


Кажется, что ваш код лучше писать как:

class Field( object ):
    def __init__( self, a, b ):
        self.a = a
        self.b = b
        self.field = Field.buildField()

    @classmethod
    def buildField(cls):
        field = [0,0,0]
        return field

class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__(a, b)
        self.field = Background.buildField(c)

    @classmethod
    def buildField(cls,c):
        field = [c]
        return field

a, b, c = 0, 1, 2
background = Background( a, b, c )

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

Поэтому гораздо лучше отделить buildField() от принадлежности к классу с помощью classmethod decorator или staticmethod, если вы должны вызвать эти методы в своем конструкторе.

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

Ответ 5

Overriding говорят, но это звучит как мне chaining constructors or (methods)

А также это звучит как over-writing:

Позвольте мне объяснить:

  • Свойство с именем поле будет инициализировано как [0,0,0]. Декораторы @property выглядят лучше.

  • Затем Background class over-write это свойство.


Быстрое и грязное решение

Я не знаю вашей бизнес-логики, но иногда обходной метод super class __init__ дал мне больше контроля:

#!/usr/bin/env python

class Field( object ):
    def __init__( self, a, b ):
        self.a = a
        self.b = b
        self.field = self.buildField()

    def buildField( self ):
        field = [0,0,0]
        return field

class Background( Field ):
    def __init__( self, a, b, c ):
        # super(Background, self).__init__( a, b )
        # Unfortunately you should repeat or move initializing a and b
        # properties here
        self.a = a
        self.b = b

        self.field = self.buildField( c )

    def buildField( self, c ):
        # You can access super class methods 
        assert super(Background, self).buildField() == [0,0,0]
        field = [c]
        return field


a, b, c = 0, 1, 2
bg = Background(a,b,c)
assert bg.field == [2]

Использование свойств

Имеет более чистый синтаксис.

#!/usr/bin/env python

class Field( object ):

    @property
    def field(self):
        return [0,0,0]


    def __init__( self, a, b ):
        self.a = a
        self.b = b


class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__( a, b )
        self.c = c

        assert  (self.a, self.b, self.c) == (0,1,2)  # We assigned a and b in 
                                                   # super class __init__ method

        assert super(Background, self).field == [0,0,0]
        assert self.field == [2]

    @property
    def field(self):
        return [self.c]


a, b, c = 0, 1, 2

background = Background( a, b, c )

print background.field