Python, Overriding an inherited class method

帅比萌擦擦* 提交于 2019-12-20 08:08:57

问题


I have two classes, Field and Background. They look a little bit like this:

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 )

This error is pointing to Field's buildField():

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

I expected Background init() to be called first. To pass "a, b" to Fields init(), Field to assign a and b then to assign a list with three 0's in it to field. Then for Background's init() to continue, to then call its own buildField() and override self.field with a list containing c.

It seems I don't fully understand super(), however i was unable to find a solution to my issue after looking at similar inheritance problems on the web and around here.

I expected behavior like c++ where a class can override a method that was inherited. How can i achieve this or something similar.

Most issues I found related to this were people using double underscores. My experience with inheritance with super is using the inherited class init() to just pass different variables to the super class. Nothing involving overwriting anything.


回答1:


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.




回答2:


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.




回答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.




回答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.




回答5:


Overriding is talked about but it sounds like to me chaining constructors or (methods)

And also it sounds like over-writing properties:

Let me explain:

  • A property named field will be initialized as [0,0,0]. @property decorators looks better fit.

  • Then, Background class over-write this property.


Quick and Dirty Solution

I do not know your business logic but sometimes by-passing super class's __init__ method gave me more control:

#!/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]

Using properties

Has more clean syntax.

#!/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's __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


来源:https://stackoverflow.com/questions/12764995/python-overriding-an-inherited-class-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!