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(), чтобы просто передать разные переменные суперклассу. Ничего не связано с перезаписью чего-либо.
Ответы
Ответ 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