Python, Overriding an inherited class method
Solution 1:
I expected Background init() to be called. To pass "a, b" to Fields init(), Field to assign a and b
So far, so good.
then to assign a list with three 0's in it to field.
Ah. This is where we get the error.
self.field = self.buildField()
Even though this line occurs within Field.__init__
, self
is an instance of Background
. so self.buildField
finds Background
's buildField
method, not Field
's.
Since Background.buildField
expects 2 arguments instead of 1,
self.field = self.buildField()
raises an error.
So how do we tell Python to call Field
's buildField
method instead of Background
's?
The purpose of name mangling (naming an attribute with double underscores) is to solve this exact problem.
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)
The method name __buildField
is "mangled" to _Field__buildField
inside Field
so inside Field.__init__
,
self.field = self.__buildField()
calls self._Field__buildField()
, which is Field
's __buildField
method. While similarly,
self.field = self.__buildField(c)
inside Background.__init__
calls Background
's __buildField
method.
Solution 2:
Coming from a C++ perspective, there might be two misconceptions here.
First, a method with the same name and different signature does not overload it like in C++. If one of your Background objects tries to call buildField with no arguments, the original version from Field will not be called -- it has been completely hidden.
The second issue is that if a method defined in the superclass calls buildField, the subclass version will be called. In python, all methods are bound dynamically, like a C++ virtual
method.
Field's __init__
expected to be dealing with an object that had a buildField method taking no arguments. You used the method with an object that has a buildField method taking one argument.
The thing with super
is that it doesnt change the type of the object, so you shouldn't change the signature of any methods that the superclass' methods might call.
Solution 3:
I expected Background init() to be called
Actually Background init()
is getting called..
But take a look at your Background class..
class Background( Field ):
def __init__( self, a, b, c ):
super(Background, self).__init__( a, b )
self.field = self.buildField( c )
So, the first statement of __init__
is invoking the super class(Field)
init method.. and passing the self
as argument.. Now this self
is actually a reference of Background class
..
Now in your Field 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()
Your buildField()
method is actually invoking the one in the Background class.. This is because, the self
here is instance of Background
class(Try printing self.__class__
in your __init__
method of Field class
).. As you passed it while invoking the __init__
method, from Background
class..
That's why you are getting error..
The error "TypeError: buildField() takes exactly 2 arguments (1 given).
As you are not passing any value.. So, only value passed is the implicit self
.
Solution 4:
The super(Background, self).__init__( a, b )
will invoke:
def __init__( self, a, b ):
self.a = a
self.b = b
self.field = self.buildField()
in Field
. However, self
here refers to the background
instance, and self.buildField()
is in fact calling buildField()
of Background
, which is why you get that error.
It seems to be that your code should be better written as:
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 )
If you can't allow the base constructor to finish then it signals that the design is flawed.
It is therefore much better to separate buildField()
to belong to the class by using classmethod
decorator or staticmethod
, if you have to call these methods in your constructor.
However, if your base class constructor does not invoke any instance method from within, you can then safely overwrite any method of this base class.