Inheritance: trying to keep DRY

Inheritance. The word alone fills me with expectation and a sense that I can benefit from the hard graft of others! There will be some price to pay, lets call it an inheritance tax, but once I’ve paid that inevitable price, I can reap the rewards of my forefathers.

I like the idea of inheritance, and creating base classes that siblings inherit from. It fits in with my view of the world that certain objects have certain properties, whether they are aware of them or use them, or not. I can see how the use of inheritance can keep the code-base DRY, and control or mandate a data contract.

Base class #

So I have an animal. It’s a generic class that has the properties species and voice, as most animals can be described in these terms.

class Animal(object):                                                           
    def __init__(self, species, voice):                                    
        self.species = species                                                  
        self.voice = voice                                                      

        print(f"Here is a {species} and it {voice}.")

Simple inheritance #

I can now create a new class that inherits these properties irrelevant of the animal type.

class Dog(Animal):
    pass

And I can now happily create instances of dogs, cats, birds, etc. without lots of code repetition.

rex = Dog("Dog", "barks")

Output: “Here is a Dog and it barks”

Inheritance with additional properties #

I’m happy with my Dog class, but I now want a wolf! And as we all know, wolves have big, sharp teeth, that are all the better to eat you with!

I still want to inherit from my Animal class as a wolf is still a species of animal and I know it will howl all night long. But I want to capture those gnarly teeth and print a comment about them.

class Wolf(Animal):                                                                            
    def __init__(self, species, voice, teeth):                                  
        self.teeth = teeth                                                                            

        print(f"This {species} {voice}, and it has {teeth} teeth!")         

I create my Wolf class that is a type of Animal but when this class is instantiated, it will have the additional argument; teeth that is assigned to the instance. (But why didn’t the code run and print the Animal class print statement…?)

wolfy = Wolf("Wolf", "howl", "sharp")

Output: “This Wolf howls, and it has sharp teeth!”

The mute fish #

OK, so now I have a problem. I’ve realised I want a Fish instance, but fishes don’t have a voice, and blowing bubbles doesn’t count. I’ve suggested in my Animal base class that I need two arguments, species and voice, but I want to instantiate my Fish class only with species.

class Fish(Animal):                                                             
    def __init__(self, species):                                                

    print(f"This {species} is mute.") 

This is not a problem after all. As long as I only specify one argument when the instance is created, the fish can be created, as my Animal template allows room for two.

bruce = Fish("fish")

Output: “This fish is mute”

But I wanted the print statement too… #

Bruce is a mute fish, but I still want the print statement to confirm that in line with the other instances. Therefore, I want to access the functions and code from the Animal class as well, rather than writing a fish-specific print statement, After all, I want my code to be DRY.

Here is can use Python’s builtin function, super() to look up the hierarchy tree and capture this print statement. (I appreciate that in more useful program, my base class may have some useful methods that I could access, such as a Shape class with functions for calculating areas).

Anyway, I create the fish instance using the species argument as before, but capturing the code to execute as well.

class Fish(Animal):                                                             
    def __init__(self, species):                                                

    print(f"This {species} is mute.") 
    super(Fish, self).__init__(species)

Now I have got a problem!

Output:
“Traceback (most recent call last):
File "animals.py”, line 32, in
bruce = Fish(“Fish”)
File “animals.py”, line 27, in init
super(Fish, self).init(species)
TypeError: init() missing 1 required positional argument: ‘voice’“

My Fish class now does need the voice argument but can’t find it during instantiation. As the fish is still mute, I can’t add one, but fortunately I can provide a default value for the argument, which I’ll set as None.

This base class will now cater for fish, crabs, and worms too, (although I’ll need to rethink my print statement grammar a bit)!

class Animal(object):                                                               
    def __init__(self, species, voice=None):                                    
        self.species = species                                                  
        self.voice = voice                                                      

        print(f"Here is a {species} and it {voice}.") 

Output:
"This fish is mute.
Here is a fish and it None.”

 
1
Kudos
 
1
Kudos

Now read this

Python testing (pt 1)

As someone who is a big fan of Behaviour Driven Development (BDD) and Test-Driven Development (TDD) for robust and context specific development, I really need to ensure I am comfortable with a python test framework that I can have in my... Continue →