0

I'm following a project in Python Crash Course that demonstrates how to use CSV files. The code below successfully populates the dates list with what I believe are datetime objects. For example, this is the first element of the dates list: datetime.datetime(2014, 1, 1, 0, 0). Here is the functional code:

import csv
from datetime import datetime

filename = 'sitka_weather_2014.csv'
with open(filename) as f:
    reader = csv.reader(f)

    # Move onto the next row as the first contains no data
    next(reader)

    dates = []
    for row in reader:
        try:
            date = datetime.strptime(row[0], "%Y-%m-%d")
        except ValueError:
            print(date, "missing data")
        else:
            dates.append(date)

    print(dates[0])

The output of this code is: 2014-01-01 00:00:00

Now, I wanted to implement this project with several different files, and practice with OO design principles. I set up a parent class WeatherData that has two attributes: a list called data and a string called filename. The WeatherData class will populate the data list with the a specific column from the CSV file. Next I created a child class called WeatherLocation that inherits from WeatherData. WeatherLocation has three attributes:

  • A list called highs that stores the max temperatures
  • A list called lows that stores the low temperatures
  • A list called dates that stores the dates

If we look at the set_data method of WeatherData we'll see that the same logic as the code above is implemented. When the set_dates method of WeatherLocation is called, the same row number is passed, and fetching_dates is set to True so the correct section of the if statement is executed. The code is shown below:

import csv
import os.path
from datetime import datetime


class WeatherData:

    def __init__(self, filename):
        self.data = []
        self.filename = self.give_file(filename)

    def give_file(self, filename):
        """Method checks for existence of file before setting the 'filename'
        attribute to the argument
        """
        if not os.path.isfile(filename):
            print("The file " + filename + " could not be found")
        else:
            return filename

    def set_data(self, row_number, fetching_dates=False):
        """Sets the data attribute to a list of data selected by the program"""
        if not self.filename:
            print("You must call give_file() and provide it a filename"
                  + " before calling this method")

        with open(self.filename) as f:
            reader = csv.reader(f)

            # Call next method so we can skip the header_column and get
            # into the data
            next(reader)

            for row in reader:
                if fetching_dates:
                    try:
                        date = datetime.strptime(row[row_number], "%Y-%m-%d")
                    except ValueError:
                        print(date, "missing data")
                    else:
                        self.data.append(date)
                else:
                    try:
                        datum = int(row[row_number].strip())
                    except ValueError:
                        print(datum, "missing data")
                    else:
                        self.data.append(datum)

    def get_data(self):
        return self.data


class WeatherLocation(WeatherData):
    def __init__(self, filename):
        super().__init__(filename)
        self.highs = self.set_highs()
        self.lows = self.set_lows()
        self.dates = self.set_dates()

    def set_highs(self):
        super().set_data(1)
        return super().get_data()

    def set_lows(self):
        super().set_data(3)
        return super().get_data()

    def set_dates(self):
        super().set_data(row_number=0, fetching_dates=True)
        return super().get_data()

sitka = WeatherLocation('sitka_weather_2014.csv')
print(sitka.dates[0])

Unfortunately, the output of this code is 46. What am I doing wrong?

Craig K.
  • 13
  • 5

2 Answers2

2

The problem is your superclass WeatherData has a data attribute which WeatherLocation inherits. Each time you set_data you are modifying the same data attribute. When you assign the values in your WeatherLocation subclass (e.g. self.highs = self.set_highs()), you are only returning a reference to the data attribute.

When you print(sitka.dates[0]) its getting the first element of the instance sitka's dates, which is really just a reference to sitka.dates. The __init__ calls self.set_highs() first (and is the first thing to modify data) so you are really printing the first high. If you print(sitka.data[0]) you should see the same value (e.g. 46).

If you print the full list of data (print(sitka.data)) you should see a list of highs, lows, and dates in that order.

0

Since WeatherData.set_data() appends to the list self.data each time you run it, the first element sitka.dates[0] will be the result of set_highs(), and therefore it's an integer. The dates will come later, since you call set_dates last.

self.highs = self.set_highs()  // append integers to self.data
self.lows = self.set_lows()  // append integers to self.data
self.dates = self.set_dates()  // append datetimes to self.date

The design of your classes and especially the use of inheritance is quite confusing. Instead of using getter and setter methods, you might want to consider refactoring and using python properties instead.

Python @property versus getters and setters

Håken Lid
  • 22,318
  • 9
  • 52
  • 67
  • Also the variable name `row_number` would be better as `column_number`, and whenever you `print` an error message, you should raise a proper exception instead. – Håken Lid Jul 03 '17 at 16:38
  • You only need to use `super()` when you call a method _with the same name_ in a super class. So `super().set_data(3)` can be replaced with `this.set_data(3)` and so on. The only place in your code you need `super()` is in the `__init__()` method. – Håken Lid Jul 03 '17 at 16:48