🧑‍💻 2.5 Object-Oriented Programming (OOP) Basics

Welcome to the final notebook in the Programming Basics module! In this session, we’ll explore the fundamentals of Object-Oriented Programming (OOP), a powerful approach to writing code that is organised, modular, and reusable.


🎯 Objectives

By the end of this notebook, you should be able to:

  • Understand the core concepts of OOP: classes, objects, attributes, methods, and inheritance.
  • Write your own classes to model nutrition-related entities like foods, nutrients, and diets.
  • Apply OOP principles to create clean, readable, and maintainable code.

🧠 Why Object-Oriented Programming?

In the real world, data isn’t flat—it’s structured and interconnected.

In nutrition science, for example, you might deal with:

  • Foods that have a name, nutrient profile, and cost.
  • Nutrients that have a name, units, and dietary recommendations.
  • Diets that contain multiple foods and require specific calculations.

These aren’t just isolated data points—they are related entities. OOP helps you mirror these real-world relationships in your code.

Instead of managing a mess of separate variables and functions, you can bundle everything into a single unit (a class) and then create instances of that unit (objects) with unique data.


🧰 What Is OOP?

Object-Oriented Programming is based on the idea of “objects”—which combine data (called attributes) and behaviours (called methods). You define a class as a blueprint, then create multiple objects (instances) from it.

This makes it easy to:

  • Keep related information together
  • Reuse code through inheritance
  • Extend or update functionality without rewriting everything

🧪 Example from Nutrition

Imagine a Food class:

class Food:
    def __init__(self, name, calories):
        self.name = name
        self.calories = calories

You can now create specific food items:

apple = Food("Apple", 95)
banana = Food("Banana", 120)

Each Food object carries its own name and calories, and you can easily create new ones without writing new functions or variables each time.


🐘 Fun Fact

Think of OOP like a hippo’s diet log—each food entry is an object, and the diet is a class that keeps it all neat and trackable!
Just like real-life items (e.g. hippos, food, nutrients) have attributes (like colour, energy, or weight) and actions (like eat, swim, or digest), objects in Python do too!


🛠 What You’ll Learn in This Notebook

We’ll cover the core building blocks of OOP in Python:

  • Classes and Objects: How to define and use them
  • Attributes and Methods: How to store and interact with data
  • Inheritance: How to reuse code by creating child classes
  • Best Practices: Naming, structure, and readability

You’ll also practise applying OOP principles to realistic nutrition problems—think Nutrient, Meal, or DietPlan classes.

# Setup for Google Colab: Ensure environment is ready
# Note: This module (Programming Basics) does not require datasets
print('No dataset required for this notebook 🦛')

# Install required packages for this notebook
%pip install pandas
print('Python environment ready.')

🧱 Python Classes: A Practical Introduction

This section introduces the idea of classes in Python — the foundation of Object-Oriented Programming (OOP). In the context of food and nutrition science, classes allow us to model foods, nutrients, meals, and other complex entities in a structured and reusable way.


🧠 What Is a Class?

A class is a blueprint for creating objects. Think of it as a cookie cutter — it defines the shape, and you can make as many cookies (objects) as you want from it.

Each object created from a class is called an instance. The instance has attributes (data) and methods (functions it can perform).


📦 Defining a Basic Class

Here’s a simple example of a class that represents a FoodItem.

class FoodItem:
    def __init__(self, name, calories):
        self.name = name
        self.calories = calories

    def describe(self):
        return f'{self.name} contains {self.calories} kcal per serving.'
  • __init__ is the constructor, a special method used to initialise the object.
  • self refers to the current instance being created.
  • .describe() is a method that returns a string description of the food item.

🍏 Creating and Using an Object

Let’s create an instance of FoodItem.

apple = FoodItem("Apple", 95)
print(apple.describe())

Output:

Apple contains 95 kcal per serving.

🧪 Exercise: Define Your Own Class

Create a class called Drink with attributes: - name (e.g., “Orange Juice”) - volume_ml (e.g., 250) - sugar_g (e.g., 22)

Add a method sugar_concentration() that returns grams of sugar per 100 mL.

# Your code here
💡 Show Answer

Solution code below:

class Drink:
    def __init__(self, name, volume_ml, sugar_g):
        self.name = name
        self.volume_ml = volume_ml
        self.sugar_g = sugar_g

    def sugar_concentration(self):
        return (self.sugar_g / self.volume_ml) * 100

juice = Drink("Orange Juice", 250, 22)
print(f'{juice.name}: {juice.sugar_concentration():.1f} g sugar per 100 mL')

🧭 Summary

  • A class is a way to bundle data and functionality together.
  • Each object is an instance of a class.
  • Use __init__ to define how each object is constructed.
  • Add methods to define behaviours.
  • OOP helps you organise code better, especially in complex projects.

In the next part, we’ll explore methods, attributes, and inheritance in more detail.

🛠️ Adding Methods and Attributes in Python Classes

Once you’ve defined a basic class, you can add more functionality by defining methods (functions inside classes) and attributes (data stored in each object). This makes your classes more useful and more descriptive.


🧃 Example: Extending the Food Class

Let’s take our basic Food class and add:

  • A new attribute for category (e.g., ‘Fruit’, ‘Vegetable’)
  • A method to update the calorie value based on serving size
class Food:
    def __init__(self, name, calories, category):
        self.name = name
        self.calories = calories
        self.category = category  # new attribute

    def describe(self):
        return f"{self.name} ({self.category}) has {self.calories} calories per serving."

    def scale_calories(self, factor):
        self.calories = int(self.calories * factor)
        return f"Updated {self.name} to {self.calories} calories."

Now let’s create and use some objects:

carrot = Food("Carrot", 41, "Vegetable")
mango = Food("Mango", 60, "Fruit")

print(carrot.describe())     # Carrot (Vegetable) has 41 calories per serving.
print(mango.describe())      # Mango (Fruit) has 60 calories per serving.

# Scale the calorie value
print(carrot.scale_calories(2))  # Updated Carrot to 82 calories.
print(carrot.describe())         # Carrot (Vegetable) has 82 calories per serving.

🧠 Best Practices

  • Add only the attributes and methods you need—not everything!
  • Try to keep your class focused on one concept.
  • Use meaningful method names like describe, scale_calories, increase_amount, etc.
  • Add a docstring to methods if they are not self-explanatory.

🔍 Hidden Tip: Why use methods instead of changing attributes directly?

Using a method like scale_calories() lets you control and validate how attributes are changed, and makes the code more readable. It also makes it easier to maintain or adjust later.

🥗 Inheritance in Python: Food and Diet Classes

In Object-Oriented Programming (OOP), inheritance is a powerful concept where one class (called the child or subclass) derives properties and behaviour (methods and attributes) from another class (called the parent or base class).

This helps you avoid writing the same code again, making your code more organised and modular.


🍎 Step-by-Step: Building the Food Class

First, we define a general class that represents any food item:

# Parent class
class Food:
    def __init__(self, name, calories, category):
        self.name = name
        self.calories = calories
        self.category = category

    def describe(self):
        return f'{self.name} ({self.category}) has {self.calories} calories per serving.'
  • __init__ is the constructor. It runs when an object is created.
  • self.name, self.calories, and self.category are attributes that belong to a specific object.
  • The method describe returns a string that summarises the object.

🌿 Creating a Subclass: Diet

Now let’s say we want to add an extra piece of information for some foods that are part of a diet plan, such as "Low-Carb" or "Keto". Instead of rewriting the whole Food class, we create a child class Diet that inherits from it.

# Child class that inherits from Food
class Diet(Food):
    def __init__(self, name, calories, category, diet_type):
        # super() refers to the parent class (Food) and calls its constructor
        super().__init__(name, calories, category)
        self.diet_type = diet_type

    # Overriding the describe method to include diet_type
    def describe(self):
        return f'{self.name} ({self.category}, {self.diet_type}) has {self.calories} calories per serving.'

🧠 What’s Going On Here?

  • Diet(Food) means: Diet is a subclass of Food.
  • super().__init__(...) calls the parent class’s __init__ method so we don’t have to retype the name, calories, and category assignments. This is good practice and prevents duplication.
  • We then add a new attribute diet_type specific to the Diet class.
  • We also override the describe method to include the diet type.

🧪 Testing the Subclass

keto_avocado = Diet("Avocado", 160, "Fruit", "Keto")
print(keto_avocado.describe())

Output:

Avocado (Fruit, Keto) has 160 calories per serving.

This shows that the new class works just like Food, but with extra features.


✅ Summary

Term Meaning
class A blueprint for creating objects
__init__ A constructor that runs when the object is created
super() A function that gives access to the parent class’s methods
Inheritance A way for one class to reuse or extend another class’s code
Override Redefining a method in the child class to change its behaviour

🧾 Summary: Object-Oriented Programming in Python

By now, you’ve learned the foundational concepts of Object-Oriented Programming (OOP), including:

  • Classes: Blueprints for creating objects (like templates for food items).
  • Objects: Instances of classes with their own data (attributes) and actions (methods).
  • Attributes: Variables that belong to a class/object.
  • Methods: Functions that belong to a class/object and operate on its data.
  • Inheritance: Allows a new class to inherit methods and attributes from an existing class, promoting code reuse.

🧪 Exercises

1. Custom Food Class

Create a Snack class that inherits from Food and includes: - A new attribute snack_type (e.g., ‘sweet’, ‘salty’) - A method is_healthy() that returns True if calories are below 200.

Test it with examples like:

popcorn = Snack('Popcorn', 150, 'Grain', 'salty')
print(popcorn.is_healthy())  # Should return True

2. Composite Object

Create a Meal class that: - Takes a list of Food or Diet objects - Has a method total_calories() that sums their calories


3. Describe All

Create a function describe_meal(meal) that loops through the foods in a Meal object and prints their descriptions.


✅ Conclusion

Object-Oriented Programming makes your Python code: - Easier to organise - More reusable - Cleaner and more intuitive

You can now model real-world nutrition problems in a structured and scalable way.

🔍 Advanced: Magic Methods and Encapsulation
  • Magic methods like __str__ or __repr__ let you control how objects are printed.
  • Encapsulation helps you protect internal states with naming conventions (e.g., _private, __very_private).
  • You can also use @property decorators to create computed attributes.

🎉 Well done! You’re ready to tackle real-world nutrition modelling using OOP!