Classes#

In the previous chapters we have used variables that are more than just variables: for example we have seen that some functions are attached to strings like the capitalize function:

mystring = 'this is my text'
mystring.capitalize()
'This is my text'

We have also seen the Path object that we use to define paths that are not just strings:

from pathlib import Path

current_path = Path('../Day2').absolute()

The path objects have properties attached to them:

current_path.parent
PosixPath('/Users/gw18g940/GoogleDrive/DSL/Trainings/Crash_Course_DataSciPy/Day1/..')

and functions:

current_path.glob('*')
<generator object Path.glob at 0x106a93bc0>

Both the simple string variables and the path objects defined above are actually instances of objects, where objects can be broadly seen as variables to which multiple properties and functions, called methods are attached. Python has the peculiarity that every variable actually is an object instance! You will find many packages that provide objects with diverse functionalities, and sometimes you will even have to create your own classes or sub-classes, for example when creating deep learning models.

Classes, instances etc.#

Classes are working in the following way.

First, one defines an example of an object as a class, inclusing all its properties and methods. This can be seen as an “idealized” version of the object, like when you broadly describe a domestic animal: it has for example a species, a name, a height. You also know whether it has fur, which is the case of most domestic animals, and also how much it eats per day. This is how it’s done:

class Animal:
    def __init__(self, name, age, species, food_per_day, has_fur=True):
        self.species = species
        self.name = name
        self.age = age
        self.food_per_day = food_per_day
        self.has_fur = has_fur

    def food_period(self, days):
        return self.food_per_day * days

Let’s look in detail what is done here.

  1. wFirst we use the class keyword to indicate we want to create a class. We also give it the name Animal

  2. We define functions, i.e. methods, inside the class (indented). The __init__ function is special and gets executed the first time we use the class. It takes as input all the variables we want to be able to specify for a given animal, in this case: the name, age, species, quantity of food per day. The has_true parameter is optional and set to True by default as most animals do have fur. We see that insisde the function the parameters are saved in the class using the notation self.parameter. self represents the class itself hence the name.

  3. Then there is a second method food_period, which returns the food eaten by an animal over a given number of days. It uses internally, the parameter food_per_day and a parameter given by the user.

Let’s see now how we an use this class. For the moment it’s just an abstract version of an animal. We now want to create on example, or instance of that class for our cat called Woody:

woody = Animal(name='Woody', age=10, species='cat', food_per_day=0.2)

We see that this works exactly like the Path object used above, where Path to a single argument as a path string. Here we just have multiple arguments and woody is an instance of the object. We can query its parameters:

woody.age
3

And we can execute its functions. For example if we want to know how much food it eats during a week we can query:

woody.food_period(7)
1.4000000000000001

Sub-classes#

What often happens is that we want to use an existing class that contains already a lot of interesting functionalities, but we want to specialize it for a certain application. For example, we may want to have a class specialized for birds. Birds are animals, so we don’t have to redefine the same arguments as for the Animal class. We can just re-reuse it. However we now add some specialization to it. For example we can indicate if it’s a singing bird or not, or how many eggs it currently incubates.

class Bird(Animal):
    def __init__(self, name, age, species, food_per_day, has_fur=False, singing_bird=False, number_of_eggs=0):
        super().__init__(name, age, species, food_per_day, has_fur)
        self.singing_bird = singing_bird
        self.number_of_eggs = number_of_eggs

    def lay_egg(self):
        self.number_of_eggs += 1

To re-use the Animal class, we

  1. define a new normal class Bird and pass the main class to it as an argument. We see that we chose here to set has_fur to False by default.

  2. use again an __init__ function to pass arguments, including the old and new arguments

  3. use a slightly odd function super().__init__ that allows us to pass the old arguments to the main Animal class

  4. define a new function specific to birds that allows to to increase the numnber of eggs in case the bird is laying new ones.

Let’s create a bird:

finch = Bird(name='Finchy', age=2, species='finch', food_per_day=0.05, singing_bird=True)

We can now query the number of eggs it has:

current_eggs = finch.number_of_eggs

And indicate that the bird just laid an egg:

finch.lay_egg()
finch.number_of_eggs
1

Exercise#

  1. Create a Plant class with the properties height, species, water_per_week. Assuming the plan grows by 2cm for each watering, create a method computing the expected growth over a week.

  2. Create an instance of your favorite plant.

  3. Create a sub-class for Flowering plants. Add a property for the number of flowers and a method that prints “blooming!” if there’s at least one flower.