Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Practice: intro to classes

Let us suppose we have a set of weather stations that do measurements of wind speed and temperature in a weather station (for example in Paris). We need to store these measurements in a clear way so that computing statistics (maximum wind, average temperature...) is easy and not error prone. This chapter shows different ways of storing the data, its advantages and disadvantages.

The basic way: only with lists

Solution to Exercise 1
paris = [wind_values, temperature_values]

# get wind when temperature is maximal
idx_max_temp = paris[1].index(max(paris[1]))
print(f"max temp is {paris[1][idx_max_temp]}°C at index {idx_max_temp} ")
print(f"wind speed at max temp = {paris[0][idx_max_temp]} km/h")
max temp is 5°C at index 1 
wind speed at max temp = 0 km/h

Comments on this solution

Many problems:

More complex structures: mixing dicts and lists

Solution to Exercise 2
paris = {"wind": wind_values, "temperature": temperature_values}

# get wind when temperature is minimal
paris_temp = paris["temperature"]
idx_max_temp = paris_temp.index(max(paris_temp))

print(f"max temp is {paris_temp[idx_max_temp]}°C at index {idx_max_temp}")
print(f"wind speed at max temp = {paris['wind'][idx_max_temp]} km/h")
max temp is 5°C at index 1
wind speed at max temp = 0 km/h

Comments

Improve database: add functions

Solution to Exercise 3
paris = {"wind": wind_values, "temperature": temperature_values}


def max_temp(station):
    """returns the maximum temperature available in the station"""
    return max(station["temperature"])


def arg_max_temp(station):
    """returns the index of maximum temperature available in the station"""
    max_temperature = max_temp(station)
    return station["temperature"].index(max_temperature)


idx_max_temp = arg_max_temp(paris)

print(f"max temp is {max_temp(paris)}°C at index {arg_max_temp(paris)}")
print(f"wind speed at max temp = {paris['wind'][idx_max_temp]} km/h")
max temp is 5°C at index 1
wind speed at max temp = 0 km/h

Comments

Continue improving: build a init function

Solution to Exercise 4
def build_station(wind, temp):
    """Build a station given wind and temp
    :param wind: (list) floats of winds
    :param temp: (list) float of temperatures
    """
    if len(wind) != len(temp):
        raise ValueError("wind and temperature should have the same size")
    return {"wind": list(wind), "temperature": list(temp)}


def max_temp(station):
    """returns the maximum temperature available in the station"""
    return max(station["temperature"])


def arg_max_temp(station):
    """returns the index of maximum temperature available in the station"""
    max_temperature = max_temp(station)
    return station["temperature"].index(max_temperature)


paris = build_station(wind_values, temperature_values)
idx_max_temp = arg_max_temp(paris)

print(f"max temp is {max_temp(paris)}°C at index {arg_max_temp(paris)}")
print(f"wind speed at max temp = {paris['wind'][idx_max_temp]} km/h")
max temp is 5°C at index 1
wind speed at max temp = 0 km/h

Comments

Last improvement: using a class

We would like to “embed” the max_temp and the arg_max_temp in the “dictionary station” in order to address the last point.

And here comes object-oriented programming !

A class defines a template used for building object. In our example, the class (named WeatherStation) defines the specifications of what is a weather station (i.e, a weather station should contain an array for wind speeds, named “wind”, and an array for temperatures, named “temp”). paris should now be an object that answers to these specifications. Is is called an instance of the class WeatherStation.

When defining the class, we need to define how to initialize the object (special “function” __init__).

Solution to Exercise 5
class WeatherStation:
    """A weather station that holds wind and temperature

    :param wind: any ordered iterable
    :param temperature: any ordered iterable

    wind and temperature must have the same length.

    """

    def __init__(self, wind, temperature):
        """initialize the weather station.
        Precondition: wind and temperature must have the same length.
                      ValueError is raised if this is not the case
        :param wind: any ordered iterable
        :param temperature: any ordered iterable"""
        self.wind = list(wind)
        self.temp = list(temperature)
        if len(self.wind) != len(self.temp):
            raise ValueError(
                "wind and temperature should have the same size"
                f" got len(wind)={len(self.wind)} vs "
                f" len(temp)={len(self.temp)}"
            )

    def max_temp(self):
        """returns the maximum temperature recorded in the station"""
        return max(self.temp)

    def arg_max_temp(self):
        """returns the index of (one of the) maximum temperature recorded in the station"""
        return self.temp.index(self.max_temp())


paris = WeatherStation(wind_values, temperature_values)
idx_max_temp = paris.arg_max_temp()

print(f"max temp is {paris.max_temp()}°C at index {paris.arg_max_temp()}")
print(f"wind speed at max temp = {paris.wind[idx_max_temp]} km/h")
max temp is 5°C at index 1
wind speed at max temp = 0 km/h

Comments

An object (here paris) thus contains both attributes (holding data for example) and methods to access and/or process the data.

Solution to Exercise 6
class WeatherStation(object):
    """A weather station that holds wind and temperature"""

    def __init__(self, wind, temperature):
        """initialize the weather station.
        Precondition: wind and temperature must have the same length
                      ValueError is raised if this is not the case
        :param wind: any ordered iterable
        :param temperature: any ordered iterable"""
        self.wind = [x for x in wind]
        self.temp = [x for x in temperature]
        if len(self.wind) != len(self.temp):
            raise ValueError(
                "wind and temperature should have the same size"
                f" got len(wind)={len(self.wind)} vs "
                f" len(temp)={len(self.temp)}"
            )

    def perceived_temp(self, index):
        """computes the perceived temp according to
        https://en.wikipedia.org/wiki/Wind_chill
        i.e. The standard Wind Chill formula for Environment Canada is:
        apparent = 13.12 + 0.6215*air_temp - 11.37*wind_speed^0.16 + 0.3965*air_temp*wind_speed^0.16

        :param index: the index for which the computation must be made
        :return: the perceived temperature"""
        air_temp = self.temp[index]
        wind_speed = self.wind[index]
        # Perceived temperature does not have a sense without wind...
        if wind_speed == 0:
            apparent_temp = air_temp
        else:
            apparent_temp = (
                13.12
                + 0.6215 * air_temp
                - 11.37 * wind_speed**0.16
                + 0.3965 * air_temp * wind_speed**0.16
            )
        # Let's round to avoid trailing decimals...
        return round(apparent_temp, 2)

    def perceived_temperatures(self):
        """Returns an array of perceived temp computed from the temperatures and wind speed data"""
        apparent_temps = []
        for index in range(len(self.wind)):
            # Reusing the method perceived_temp defined above
            apparent_temperature = self.perceived_temp(index)
            apparent_temps.append(apparent_temperature)
        return apparent_temps

    def max_temp(self, perceived):
        """returns the maximum temperature record in the station"""
        if perceived:
            apparent_temp = self.perceived_temperatures()
            return max(apparent_temp)
        else:
            return max(self.temp)

    def arg_max_temp(self, perceived):
        """returns the index of (one of the) maximum temperature record in the station"""
        if perceived:
            temp_array_to_search = self.perceived_temperatures()
        else:
            temp_array_to_search = self.temp
        return temp_array_to_search.index(self.max_temp(perceived))

Comments

Coming next: inheritance

What if we now have a weather station that also measure humidity ?

Do we need to rewrite everything ?

What if we rewrite everything and we find a bug ?

Here comes inheritance