简体   繁体   中英

How to call method from a derived class in the base class?

I am trying to write a clean code and by doing this learning one or two things.

class Building:
    def __init__(self, db):
        self.__db = db
    def decode(self, value):
        field = dict()
        for key in self.__db:
            field[key] = int(value)
        return field

class Apartment(Building):
    def __init__(self, db):
        super().__init__(db)

    def decode(self, str_value):
        field = super().decode(str_value)
        field = self.scaling(field)     
        return field         

    def scaling(self, field):
        field['size'] /= 1000
        return field

class Home(Building):
    def __init__(self, db):
        super().__init__(db)

    def decode(self, str_value):
        field = super().decode(str_value)
        field = self.scaling(field)
        return field

    def scaling(self, field):
        field['garden'] /= 25
        return field

def main():
    apartment_db = { 'size' : None}
    home_db = { 'garden' : None}
    app = Apartment(apartment_db)
    home = Home(home_db)
    print(app.decode('5'))
    print(home.decode('100'))

if __name__ == "__main__":
    main()

Output:

 {'size': 0.005} {'garden': 4.0}

As it can be seen, each derived class have its own databank and its own scaling (no scaling is in my opinion is scaling as well).

My thinking problem is that the base class do not know the scaling implementation, which is defined in the derived class.

If i could pass the scaling implementation of the derived class to the base class, then my decode method in the derived class will be very simply:

class Apartment(Building):
    def decode(self,str_value):
        return super().decode(str_value)

Is there a way to do it in python3.7?

Yes it is possible.

If you are fine with adding a new method to the Building class, a simple way would be add the scaling method to it as well. Now the derived classes, which don't have their own definition for scaling , will just use the Building 's trivial implementation:

class Building:
    def __init__(self, db):
        self.__db = db

    def decode(self, value):
        field = dict()
        for key in self.__db:
            field[key] = int(value)
        return self.scaling(field)

    def scaling(self, value):
        return value

If you don't want to add a method to Building , you still have some options. The look before you leap way to do this, would be to check if self has the attribute 'scaling' , and then pass field through self.scaling if that is true. This works becuse self is always the instance which we are calling the method on, so it will have the attributes that are defined for that object's class. Here's a quick implementation of that:

class Building:
    def __init__(self, db):
        self.__db = db

    def decode(self, value):
        field = dict()
        for key in self.__db:
            field[key] = int(value)

        if hasattr(self, 'scaling'):
            field = self.scaling(field)

        return field

Alternatively, the easier to ask forgiveness than permission way, would be to use try-except , and catch the AttributeError and do no scaling when the method doesn't exist:

class Building:
    def __init__(self, db):
        self.__db = db

    def decode(self, value):
        field = dict()
        for key in self.__db:
            field[key] = int(value)

        try:
            field = self.scaling(field)
        except AttributeError:
            pass

        return field

This can be also done without adding any methods and even without using an if -statement or try-except , with a kinda funny hack. This passes a lambda, which just returns its argument (=identity function), as the 3rd argument for the gettattr function. This 3rd argument is the default value that gettattr returns when it cannot find the given attribute. Then we simply call the return value of the gettattr with the field as an argument and return the result of that:

class Building:
    def __init__(self, db):
        self.__db = db

    def decode(self, value):
        field = dict()
        for key in self.__db:
            field[key] = int(value)
        return getattr(self, 'scaling', lambda x: x)(field)

All of the above ways will let you remove the __init__ and decode methods from the derived classes and have them just implement scaling if necessary. They will also all give the same results when used like here:

class Building:
    # Any of the above implementations,
    # will give the exact same results.

class Apartment(Building):
    def scaling(self, field):
        field['size'] /= 1000
        return field

class Home(Building):
    def scaling(self, field):
        field['garden'] /= 25
        return field

def main():
    apartment_db = { 'size' : None}
    home_db = { 'garden' : None}
    app = Apartment(apartment_db)
    home = Home(home_db)
    print(app.decode('5'))
    print(home.decode('100'))

if __name__ == "__main__":
    main()

Output:

 {'size': 0.005} {'garden': 4.0}

Inheritance works from Base class to Derived class, so in short, I don't believe you can call a Derived-class method from the Base-class. If we are talking about instances then maybe there are some tricks.

It is also good practice to think Derived class as a specialization of the Base class, so that common behavior remains in the Base class and you just tweak (aka override) some parameters and methods where it is needed, and for the particular purpose. This makes the entire code more readable and maintainable.

For your particular case, I can think of a different solution, where you could embed the scaling directly in your Base class, and change the scaling factor only in your derived classes, something like:

class Base(object):
    def __init__(self):
        self.scaling = 1

    def decode(self, value):
        field = { 'distance' : int(value)/self.scaling }
        return field


class Derived(Base):
    def __init__(self):
        self.scaling = 1000

base = Base()
print(base.decode(5))

derived = Derived()
print(derived.decode(5))

All you need to change, in this case, is the self.scaling value in any class you derive from Base . Your real-world scenario might be more complex, but I believe this paradigm would serve you better.

There are two approaches you can take. First, make scaler a function argument to decode ; it doesn't need to be a method, because you aren't using its self argument. (Although for each derived class implementation, I'll define each class's scaler as a private static method. Note, though, that you don't need a derived class simply to define a scaler; you can instantiate Building directly and pass an appropriate function directly to Building.decode )

class Building:
    def __init__(self, db):
        self.__db = db

    def decode(self, value, scaler=lambda x: x):
        field = dict()
        for key in self.__db:
            field[key] = int(value)
        return scaler(field)


class Apartment(Building):
    @staticmethod
    def _scale_by_1000(d):
        d['size'] /= 1000
        return d

    def decode(self, str_value):
        return super().decode(str_value, self._scale_by_1000)


class Home(Building): 
    @staticmethod
    def _scale_by_25(d):
        d['garden'] /= 25
        return d

    def decode(self, str_value):
        return super().decode(str_value, self._scale_by_25)

Alternately, define a do-nothing scaling method in Building , and let derived class override it as necessary.

class Building:
    def __init__(self, db):
        self.__db = db

    def decode(self, value):
        field = dict()
        for key in self.__db:
            field[key] = int(value)
        return self.scaling(field)

    def scaling(self, d):
        return d


class Apartment(Building):
    def scaling(self, field):
        field['size'] /= 1000
        return field


class Home(Building):
    def scaling(self, field):
        field['garden'] /= 25
        return field

Note that in both cases, I've dispensed with overriding methods like __init__ where the derived class did nothing but use super to call the parent method with the same arguments. Inheritance ensures that the parent will be called if no override is defined.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM