← Back to Blog

Calories Tracker Update — Round 2/2

The class has a constructor, a load method, and an add method. Time to complete it — reports, export, and menu.

The mapping from Round 1 holds. Every function becomes a method. Every reference to food_dict becomes self.food_dict. Every call to another function becomes self.method().

The remaining methods

    def last_days_details(self):
        today = datetime.datetime.now()
        last_days_dict = {}

        for n in range(0, 8):
            reference_day = (today - datetime.timedelta(days=n)).strftime("%Y-%m-%d")
            for key in self.food_dict:
                if reference_day == key.split(",")[0]:
                    last_days_dict[key] = self.food_dict[key]

        return last_days_dict

    def print_details(self):
        food_log = self.last_days_details()
        for key in food_log:
            print(f"{key.split(',')[0]} - {food_log[key][0]} - {food_log[key][1]}{food_log[key][2]} - {food_log[key][3]} kcal")

    def last_days_sum(self):
        today_string = ""
        while not today_string:
            today_string = input("Reference date (YYYY-MM-DD): ")

        while True:
            try:
                calories_target = int(input("Your daily calories target: "))
                if calories_target > 0:
                    break
                print("Target must be greater than 0.")
            except ValueError:
                print("Invalid calories target. Enter a whole number.")

        last_days_dict = {}
        for key in self.food_dict:
            if today_string == key.split(",")[0]:
                if key.split(",")[0] not in last_days_dict:
                    last_days_dict[key.split(",")[0]] = 0
                last_days_dict[key.split(",")[0]] += self.food_dict[key][3]

        for key in last_days_dict:
            if last_days_dict[key] <= calories_target:
                cal_value = last_days_dict[key]
                last_days_dict[key] = [cal_value, "On target! Great job!"]
            else:
                cal_value = last_days_dict[key]
                cal_diff  = cal_value - calories_target
                last_days_dict[key] = [cal_value, "Over target by " + str(cal_diff) + " kcal."]

        return last_days_dict

    def print_target(self):
        food_log = self.last_days_sum()
        for key in sorted(food_log):
            print(f"Date: {key} - {food_log[key][0]} kcal consumed. {food_log[key][1]}")

    def general_report(self):
        start_string = ""
        while not start_string:
            start_string = input("Start date (YYYY-MM-DD): ")

        stop_string = ""
        while not stop_string:
            stop_string = input("Stop date (YYYY-MM-DD): ")

        rep_dict = {}
        for key in self.food_dict:
            if start_string <= key.split(",")[0] <= stop_string:
                if key.split(",")[0] not in rep_dict:
                    rep_dict[key.split(",")[0]] = [self.food_dict[key][3], {key.split(",")[1]: [self.food_dict[key][0], self.food_dict[key][1], self.food_dict[key][2], self.food_dict[key][3]]}]
                else:
                    rep_dict[key.split(",")[0]][0] += self.food_dict[key][3]
                    rep_dict[key.split(",")[0]][1][key.split(",")[1]] = [self.food_dict[key][0], self.food_dict[key][1], self.food_dict[key][2], self.food_dict[key][3]]

        return rep_dict

    def export_general(self):
        food_log = self.general_report()

        start_string = min(food_log.keys())
        stop_string  = max(food_log.keys())
        filename = f"report_{start_string}_{stop_string}.txt"

        with open(filename, "w", encoding="utf-8") as f:
            for key in sorted(food_log):
                f.write("=" * 20 + "\n")
                f.write(f"Date: {key} - {food_log[key][0]} kcal consumed.\n")
                f.write("=" * 20 + "\n")
                day_dict = food_log[key][1]
                for subkey in sorted(day_dict):
                    f.write(f"Time: {subkey} - {day_dict[subkey][0]}, {day_dict[subkey][1]}{day_dict[subkey][2]} - {day_dict[subkey][3]} kcal.\n")

        print(f"Report saved: {filename}")

The logic is identical to the procedural version. The only change — consistently, throughout — is food_dictself.food_dict and function calls → self.method().

That's the point. OOP didn't rewrite the logic. It reorganized it.

The menu

tracker = CalorieTracker("bull")
tracker.load()

while True:
    option = input("\nChoose option (1-Add, 2-Show food details, 3-Discipline analysis, 4-General report, q-Quit): ")
    if option == "1":
        tracker.add()
        print("Food log added successfully!")
    elif option == "2":
        if len(tracker.last_days_details()):
            tracker.print_details()
        else:
            print("No data available.")
    elif option == "3":
        if len(tracker.last_days_sum()):
            tracker.print_target()
        else:
            print("No data available.")
    elif option == "4":
        if len(tracker.general_report()):
            tracker.export_general()
        else:
            print("No data available.")
    elif option == "q":
        break
    else:
        print("Invalid option!")

Compare this to the procedural menu. The functions are gone — replaced by method calls on tracker. The tracker owns everything. The menu just asks.

What OOP makes possible next

The procedural version was a program. This is a component.

The difference is subtle but important. A program runs from top to bottom and stops. A component can be imported, instantiated, passed around, extended — without rewriting what's already working.

Two users, right now. Without changing a single line of class code:

tracker_bull  = CalorieTracker("bull")
tracker_guest = CalorieTracker("guest")

tracker_bull.load()
tracker_guest.load()

# two independent trackers — two files — zero conflicts

With the procedural version, this would have required two separate dictionaries, two separate sets of functions, or careful parameter passing throughout. With the class, it's two lines.

Inheritance. You've just learned it. Apply it here:

class AthleteTracker(CalorieTracker):
    def __init__(self, user, daily_goal):
        super().__init__(user)
        self.daily_goal = daily_goal

    def check_goal(self):
        # same data, same methods — plus sport-specific logic
        ...

The base tracker doesn't change. The athlete version extends it. A nutritionist version could extend it differently. Same foundation, different specializations.

Web frameworks. In Django or Flask, CalorieTracker maps almost directly to a model. self.food_dict becomes a database table. self.add() becomes a form submission handler. self.export_general() becomes a PDF download endpoint. The shape of the problem stays the same. The scale changes completely.

APIs. Wrap the class in a FastAPI or Flask route — your tracker becomes a service. Another program, another user, another device can call tracker.add() over the network. The class doesn't know or care. It just works.

You built a calorie tracker. You now have the foundation for a nutrition platform.

That's what OOP unlocks — not just cleaner code, but a structure that can grow without being rewritten.

[ login to bookmark ] // copied! 32 views · 3 min
// resources
Exercise calories_tracker_oop.py
← prev Calories Tracker Update — Round 1/2 next → Safe Paws Update — Round 1/2
// 0 comments
// No comments yet. Be the first.
// leave a comment

// Your comment will appear after approval.