Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pythonic way of inheriting many classes?

In an effort to code in a more python and OOP-style way, I wonder if anyone could advise me on implementing this concept please.

Let's say I have a base class for fruit, say apple and banana, which contains basic properties for the class, e.g. color. Then I want to class which inherits from fruit called juice, which adds methods and properties, e.g. volume, sugar.

A rough structure might be:

class Fruit(object):
def __init__(self, fruit_name):
    if fruit_name == 'apple': 
        self.color = 'green'
        self.sugar_content = 2
    elif fruit_name == 'banana':
        self.color = 'yellow'
        self.sugar_content = 3

Then I inherit the methods of my Fruit classes:

class Juice(Fruit):
    def __init___(self, fruit_name, volume, additives):
        PERHAPS I NEED TO USE A SUPER STATEMENT HERE TO PASS THE fruit_name parameter (which is 'apple' or 'banana' BACK TO THE FRUIT CLASS? I want this class to be able to access sugar_content and color of the the fruit class.
        self.volume = volume
        self.additives = additives

    def calories(self, sugar_added):
        return sugar_added + 2*self.sugar_content* self.volume # i.e. some arbitrary function using the class parameters of both this juice class and the original fruit class

So ultimately, I can create an object like:

my_juice = Juice(fruit_name='apple', volume=200, additives='sugar,salt')
print 'My juice contains %f calories' % self.calories(sugar_added=5)
print 'The color of my juice, based on the fruit color is %s' % self.color

ALTERNATIVELY, I wonder if it is better NOT to inherit at all and simply call the fruit class from a Juice class. e.g.

class Juice(object):
    def __init__(self, fruit_name, volume, additives):
        self.fruit = Fruit(fruit_name=fruit_name)
        self.volume = volume # ASIDE: is there an easier way to inherit all parameters from a init call and make them into class variables of the same name and accessible by self.variable_name calls?
        self.additives = additives

    def calories(self, sugar_added):
        return sugar_added + 2*self.fruit.sugar_content* self.volume

In some ways, this above feels more natural as self.Fruit.sugar_content directly indicates the sugar_content is a property of the fruit. Whereas if I inherited, then I'd use self.sugar_content, which is a property of the fruit although could be confused with the sugar content of the juice class which is dependent on other factors.

OR, would it better still to have a separate class for each fruit, and put the logic statement to evaluate the fruit_name string passed to the Juice class witin the Juice class init and then use e.g.:

class Apple(object):
   self.color = 'green'

class Banana(object):
    self.color = 'yellow'

class Juice(object):
    def __init__(self, fruit_name, other params):
         if fruit_name == 'apple':
             self.Fruit = Apple
             self.sugar_content=2
         elif fruit_name == 'banana':
             self.Fruit = Banana
             self.sugar_content=3
         # Or some easier way to simply say
             self.Fruit = the class which has the same name as the string parameter fruit_name

I appreciate all of the above would work in theory, although I'm looking for suggestions to develop an efficient coding style. In practice, I want to apply this to a more complex project not involving fruit, although the example encompasses many of the issues I'm facing.

All suggestions / tips / suggest reading links welcomed. Thanks.

like image 318
IanRoberts Avatar asked Jan 10 '23 19:01

IanRoberts


2 Answers

I think your first option is closest to a good idea, but here's an improvement:

class Fruit:

    def __init__(self, name, sugar_content):
        self.name = name
        self.sugar_content = sugar_content

Now the logic for determining how much sugar each Fruit contains is moved outside the class definition entirely. Where can it go? One option would be to sub-class:

class Apple(Fruit):

    def __init__(self):
        super().__init__("apple", 2)

Alternatively, if the only differences between the Fruits are name and sugar content, just create instances and feed in the data (e.g. from a file):

apple = Fruit("apple", 2)

Now how about the Juice. This is composed of Fruit, but isn't itself actually Fruit, so inheritance isn't a good fit. Try:

class Juice:

    def __init__(self, fruits):
        self.fruits = fruits

    @property
    def sugar_content(self):
        return sum(fruit.sugar_content for fruit in self.fruits)

You can pass an iterable of Fruit instances to make a Juice:

>>> mixed_fruit = Juice([Fruit("apple", 2), Fruit("banana", 3)])
>>> mixed_fruit.sugar_content
5

You can then extend this to encompass ideas like:

  • How much of each Fruit goes into the Juice;
  • What the volume of the Juice is (another @property calculated from the ingredients?); and
  • What additives (water, sugar, etc.) are present.

It would be sensible to work in consistent proportional units, e.g. sugar_content in grams of sugar per gram of Fruit (i.e. percentage).

like image 113
jonrsharpe Avatar answered Jan 30 '23 06:01

jonrsharpe


Another way to think about this is with three entities Fruit, Juice, and Juicer. The Juicer is responsible for taking a certain Fruit and creating a Juice of the corresponding type Orange => OrangeJuice. Then you could have two hierarchies of classes with Orange inheriting from Fruit and OrangeJuice inheriting from Juice with Juicer essentially mapping between them.

This gets you around a lot of tricky problems that your proposed solution would generate. For instance, if I had a class called Slicer that consumed some Fruit produced a FruitSalad(Fruit[] => FruitSalad). In your proposed abstraction I could give Slicer an OrangeJuice instance (which you propose as a type of Fruit) and expect it to spit out a delicious FruitSalad but instead all I get is a soggy counter top.

like image 45
nsfyn55 Avatar answered Jan 30 '23 08:01

nsfyn55