{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Object-Oriented Programming (OOP) Basics\n", "\n", "Object-Oriented Programming (OOP) is a programming paradigm that organizes code around \"objects\" - which combine data (attributes) and functions (methods) that operate on that data. Think of objects as self-contained units that represent real-world entities or concepts. In Python, everything is an object, including basic data types like integers and strings. Therefore, we have been using OOP concepts all along without being explicit about it.\n", "\n", "\n", "### Classes and Objects\n", "\n", "A **class** is like a blueprint or template for creating objects. An **object** is a specific instance created from that class. For example, if \"Car\" is a class, then \"my_toyota\" and \"your_honda\" would be objects (instances) of that class.\n", "\n", "Here's a simple example of defining a class and creating objects from it:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2026-01-19T18:20:30.454917Z", "iopub.status.busy": "2026-01-19T18:20:30.454645Z", "iopub.status.idle": "2026-01-19T18:20:30.461691Z", "shell.execute_reply": "2026-01-19T18:20:30.461234Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Deposited $200. New balance: $1200\n", "Withdrew $300. New balance: $900\n", "Alba's balance: $900\n", "Jesus's balance: $500\n" ] } ], "source": [ "# Define a class\n", "class BankAccount:\n", " def __init__(self, owner, balance=0):\n", " self.owner = owner\n", " self.balance = balance\n", "\n", " def deposit(self, amount):\n", " self.balance += amount\n", " print(f\"Deposited ${amount}. New balance: ${self.balance}\")\n", "\n", " def withdraw(self, amount):\n", " if amount > self.balance:\n", " print(\"Insufficient funds!\")\n", " else:\n", " self.balance -= amount\n", " print(f\"Withdrew ${amount}. New balance: ${self.balance}\")\n", "\n", "# Create objects (instances)\n", "account1 = BankAccount(\"Alba\", 1000)\n", "account2 = BankAccount(\"Jesus\", 500)\n", "\n", "# Use methods\n", "account1.deposit(200)\n", "account1.withdraw(300)\n", "\n", "# Check balances (accessing attributes)\n", "print(f\"{account1.owner}'s balance: ${account1.balance}\")\n", "print(f\"{account2.owner}'s balance: ${account2.balance}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `__init__` method is a special method called a **constructor** that runs automatically when you create a new object. The `self` parameter refers to the instance itself and is used to access its attributes and methods.\n", "\n", "### Attributes and Methods\n", "\n", "**Attributes** are variables that belong to an object and store its data. **Methods** are functions that belong to an object and define its behavior." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2026-01-19T18:20:30.499085Z", "iopub.status.busy": "2026-01-19T18:20:30.498813Z", "iopub.status.idle": "2026-01-19T18:20:30.503617Z", "shell.execute_reply": "2026-01-19T18:20:30.502920Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Alba enrolled in Artificial Intelligence and Big Data\n", "Alba enrolled in Python Programming\n", "Alba's courses: ['Artificial Intelligence and Big Data', 'Python Programming']\n" ] } ], "source": [ "class Student:\n", " def __init__(self, name, student_id):\n", " self.name = name # attribute\n", " self.student_id = student_id # attribute\n", " self.courses = [] # attribute\n", "\n", " def enroll(self, course): # method\n", " self.courses.append(course)\n", " print(f\"{self.name} enrolled in {course}\")\n", "\n", " def get_courses(self): # method\n", " return self.courses\n", "\n", "# Create and use a student object\n", "student = Student(\"Alba\", \"S12345\")\n", "student.enroll(\"Artificial Intelligence and Big Data\")\n", "student.enroll(\"Python Programming\")\n", "print(f\"{student.name}'s courses: {student.get_courses()}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Inheritance\n", "\n", "**Inheritance** is a fundamental OOP concept where a new class (called a **child** or **subclass**) can be based on an existing class (called a **parent** or **superclass**). The child class inherits all the attributes and methods of the parent class and can add new ones or modify existing behavior." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2026-01-19T18:20:30.506050Z", "iopub.status.busy": "2026-01-19T18:20:30.505828Z", "iopub.status.idle": "2026-01-19T18:20:30.510963Z", "shell.execute_reply": "2026-01-19T18:20:30.510472Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Buddy is sleeping... Zzz\n", "Generic makes a sound\n", "Buddy barks!\n", "Buddy fetches the ball\n", "Buddy is a Labrador\n" ] } ], "source": [ "# Parent class\n", "class Animal:\n", " def __init__(self, name):\n", " self.name = name\n", "\n", " def speak(self):\n", " print(f\"{self.name} makes a sound\")\n", "\n", " def sleep(self):\n", " print(f\"{self.name} is sleeping... Zzz\")\n", "\n", "# Child class inherits from Animal\n", "class Dog(Animal):\n", " def __init__(self, name, breed):\n", " super().__init__(name) # Call the parent's __init__\n", " self.breed = breed # Add a new attribute\n", "\n", " def speak(self): # Override the parent's method\n", " print(f\"{self.name} barks!\")\n", "\n", " def fetch(self): # Add a new method\n", " print(f\"{self.name} fetches the ball\")\n", "\n", "# Create objects\n", "generic_animal = Animal(\"Generic\")\n", "my_dog = Dog(\"Buddy\", \"Labrador\")\n", "\n", "# Method inheritance: Dog inherits sleep() from Animal without modification\n", "my_dog.sleep()\n", "\n", "# Method overriding: Dog has its own version of speak()\n", "generic_animal.speak()\n", "my_dog.speak()\n", "\n", "# New method: fetch() is only available in Dog\n", "my_dog.fetch()\n", "print(f\"{my_dog.name} is a {my_dog.breed}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This example demonstrates three key aspects of inheritance:\n", "\n", "- **Method inheritance**: The `Dog` class automatically gets the `sleep()` method from `Animal` without any additional code. When we call `my_dog.sleep()`, it uses the parent's implementation.\n", "- **Method overriding**: The `Dog` class defines its own `speak()` method, which replaces the parent's version. When we call `my_dog.speak()`, it prints \"barks!\" instead of \"makes a sound\".\n", "- **Method extension**: The `Dog` class adds a new `fetch()` method that doesn't exist in `Animal`.\n", "\n", "The `super()` function is used to call methods from the parent class. In the example above, `super().__init__(name)` calls the `Animal` class's constructor to initialize the `name` attribute before adding the `breed` attribute specific to dogs.\n", "\n", "While we won't create complex inheritance hierarchies in this course, understanding this concept helps when working with libraries like scikit-learn. For example, when you use a model like `LinearRegression`, it inherits from base classes that provide common methods like `fit()`, `predict()`, and `score()`. This is why all scikit-learn models share a consistent interface—they all inherit from the same base classes." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2026-01-19T18:20:30.513122Z", "iopub.status.busy": "2026-01-19T18:20:30.512917Z", "iopub.status.idle": "2026-01-19T18:20:32.322589Z", "shell.execute_reply": "2026-01-19T18:20:32.322086Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "LinearRegression methods: ['copy_X', 'fit', 'fit_intercept', 'get_metadata_routing', 'get_params']\n", "DecisionTreeRegressor methods: ['apply', 'ccp_alpha', 'class_weight', 'cost_complexity_pruning_path', 'criterion']\n" ] } ], "source": [ "# Preview: scikit-learn models use inheritance\n", "# All estimators inherit common methods from base classes\n", "from sklearn.linear_model import LinearRegression\n", "from sklearn.tree import DecisionTreeRegressor\n", "\n", "# Both models have the same interface because they inherit from the same base class\n", "lr = LinearRegression()\n", "dt = DecisionTreeRegressor()\n", "\n", "# Both have fit(), predict(), score() methods inherited from base classes\n", "print(\"LinearRegression methods:\", [m for m in dir(lr) if not m.startswith('_')][:5])\n", "print(\"DecisionTreeRegressor methods:\", [m for m in dir(dt) if not m.startswith('_')][:5])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Why Use OOP?\n", "\n", "OOP helps organize complex programs by grouping related data and functionality together. This makes code:\n", "\n", "- **More intuitive**: Objects model real-world entities\n", "- **Easier to maintain**: Changes to one class don't affect unrelated code\n", "- **Reusable**: Classes can be used in multiple parts of your program\n", "\n", "In data science, you'll often work with objects like DataFrames (from pandas), models (from scikit-learn), or plots (from matplotlib), even if you don't create your own classes frequently." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2026-01-19T18:20:32.325002Z", "iopub.status.busy": "2026-01-19T18:20:32.324701Z", "iopub.status.idle": "2026-01-19T18:20:32.328262Z", "shell.execute_reply": "2026-01-19T18:20:32.327645Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4\n" ] } ], "source": [ "# Example: You're already using OOP when working with lists!\n", "my_list = [1, 2, 3] # my_list is an object of class 'list'\n", "my_list.append(4) # append is a method\n", "my_list.sort() # sort is a method\n", "print(len(my_list)) # len works with the object's internal data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For **this course**, understanding how to use objects and their methods is more important than creating complex class hierarchies. Most of the time, you'll be **using classes created by others** (like pandas DataFrames or scikit-learn models) **rather than writing your own**.\n", "\n", "\n", "### Exercises\n", "\n", "Now that we have covered the basics of object-oriented programming in Python, here are some exercises to help reinforce your understanding of classes, objects, attributes, methods, and inheritance.\n", "\n", "1. Create a `Rectangle` class with `width` and `height` attributes. Add methods `area()` that returns the area and `perimeter()` that returns the perimeter. Create a rectangle object and test both methods.\n", "\n", "2. Create a `Counter` class with a `count` attribute that starts at 0. Add methods `increment()` to increase the count by 1, `decrement()` to decrease it by 1, and `reset()` to set it back to 0. Test your class by creating a counter and calling its methods.\n", "\n", "3. Create a `Vehicle` parent class with attributes `brand` and `year`, and a method `info()` that prints vehicle information. Then create a `Car` child class that adds a `num_doors` attribute and overrides the `info()` method to also display the number of doors.\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3", "path": "/usr/local/share/jupyter/kernels/python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.5" } }, "nbformat": 4, "nbformat_minor": 4 }