{ "cells": [ { "cell_type": "markdown", "id": "612d5b1f", "metadata": {}, "source": [ "# 12. Object-Oriented Programming\n", "\n", "**Object-Oriented Programming (OOP)** is a programming paradigm that models real-world entities as objects, each with its own properties (attributes) and behaviors (methods). OOP provides a structured and organized way to design and develop software, making it easier to manage complexity and promote code reusability.\n", "\n", "We'll learn about the following topics:\n", "\n", " - [12.1. Objects](#Objects)\n", " - [12.2. Class](#Class)\n", " - [12.3. Instance](#Instance)\n", " - [12.4. Attributes](#Attributes)\n", " - [12.5. Methods](#Methods)\n", " - [12.6. Inheritance](#Inheritance)\n", " - [12.7. Polymorphism](#Polymorphism) \n", " - [12.8. Special Methods in OOP](#Special_Methods_in_OOP)" ] }, { "cell_type": "markdown", "id": "142b8ed9", "metadata": {}, "source": [ "

\n", " \n", "

" ] }, { "cell_type": "markdown", "id": "36d75de4", "metadata": {}, "source": [ "\n", "\n", "## 12.1. Objects:" ] }, { "cell_type": "markdown", "id": "dff39dc7", "metadata": {}, "source": [ "In Python, an **object** is a fundamental building block that represents data and the operations you can perform on that data. Think of it as a container that holds both information (attributes) and actions (methods).\n", "\n", "In Python, everything is an object. Numbers, strings, lists, dictionaries, and even functions are all objects." ] }, { "cell_type": "code", "execution_count": 1, "id": "2b55c0fb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n" ] } ], "source": [ "print(type(1))\n", "print(type([]))\n", "print(type(()))\n", "print(type({}))\n", "print(type({1,}))\n", "print(type('1'))\n", "print(type(all))" ] }, { "cell_type": "markdown", "id": "953c6e2b", "metadata": {}, "source": [ "\n", "\n", "## 12.2. Class:" ] }, { "cell_type": "markdown", "id": "89366350", "metadata": {}, "source": [ "User defined objects are created using the `class` keyword. The class is a blueprint that defines the nature of a future object. It defines the attributes (data) and methods (functions) that objects of that class will have.\n", "\n", "`class ClassName:\n", " Attributes and Methods`" ] }, { "cell_type": "code", "execution_count": 2, "id": "ae2d21c1", "metadata": {}, "outputs": [], "source": [ "class Car:\n", " pass" ] }, { "cell_type": "markdown", "id": "41b0a354", "metadata": {}, "source": [ "\n", "\n", "## 12.3. Instance:" ] }, { "cell_type": "markdown", "id": "e7e86d53", "metadata": {}, "source": [ "Instance is a specific object created from a class. In other words, an instance is a specific realization or occurrence of a class. It's like a copy of the class template, with its own set of attribute values.\n", "\n", "`ClassName()`" ] }, { "cell_type": "code", "execution_count": 3, "id": "b7efb353", "metadata": {}, "outputs": [], "source": [ "car1 = Car()" ] }, { "cell_type": "code", "execution_count": 4, "id": "c8ed57fd", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "print(type(car1))" ] }, { "cell_type": "markdown", "id": "a50d1365", "metadata": {}, "source": [ "\n", "\n", "## 12.4. Attributes:" ] }, { "cell_type": "markdown", "id": "0cb4f272", "metadata": {}, "source": [ "Attributes are the properties or characteristics of an object. For example, a car object might have attributes like color, brand, and model.\n", "\n", "- **Defining Attributes**:\n", "\n", "**1. Class-level Attributes**: These attributes are shared by all instances of the class and have the same value for each instance. These are defined directly within the class body, outside of any methods.\n", "\n", "`class ClassName:\n", " attribute1 = value1 # Class level attribute\n", " attribute2 = value2 # Another class level attribute`" ] }, { "cell_type": "code", "execution_count": 5, "id": "f06940be", "metadata": {}, "outputs": [], "source": [ "class Car:\n", " #Class-level attribute\n", " wheels = 4" ] }, { "cell_type": "markdown", "id": "0c5c1251", "metadata": {}, "source": [ "**2. Instance-level Attributes**: These attributes are initialized with values provided when creating an instance of the class and can have different values for each object. These are defined within the class's constructor method **`(__init__)`**.\n", "\n", "`class ClassName:\n", " def __init__(self, argument1, argument2):\n", " self.attribute1 = argument1 # Instance-level attribute\n", " self.attribute2 = argument2 # Another instance level attribute`\n", " \n", "You can assign default values to instance attributes within the `__init__` method. Default values are used when no value is provided for an attribute when creating an instance.\n", "\n", "`class ClassName:\n", " def __init__(self, argument1=default_value):\n", " self.attribute1 = argument1`" ] }, { "cell_type": "code", "execution_count": 6, "id": "0802a94e", "metadata": {}, "outputs": [], "source": [ "class Car:\n", " #Class-level attribute\n", " wheels = 4\n", "\n", " def __init__(self, brand, model, year, color):\n", " #Instance-level attributes\n", " self.brand = brand\n", " self.model = model\n", " self.year = year\n", " self.color = color" ] }, { "cell_type": "markdown", "id": "a06296d5", "metadata": {}, "source": [ "In this example, wheels is a class-level attribute. All Car objects will have the same number of wheels (4). Brand, model, color, and year are instance-level attributes. Each Car object will have its own unique values for these attributes." ] }, { "cell_type": "markdown", "id": "4cded413", "metadata": {}, "source": [ "- **Accessing Attributes**: To call an attribute of an object in Python, you use the `.` notation.\n", "\n", "`object_instance.attribute_name`" ] }, { "cell_type": "markdown", "id": "f3a6f1d0", "metadata": {}, "source": [ "let's create an instance from Car class. \n", "Here are the two ways to pass arguments to a function in Python:\n", "\n", "1. When creating an instance of a class in Python, pass the attribute values in the same order as they are defined in the class's constructor method `(__init__)`.\n", "\n", "2. Arguments are passed by name, using the syntax `argument_name=value`. The order of keyword arguments doesn't matter." ] }, { "cell_type": "code", "execution_count": 7, "id": "109248fb", "metadata": {}, "outputs": [], "source": [ "car2 = Car('Mercedes-Benz', 'E350', 2023, 'silver')" ] }, { "cell_type": "code", "execution_count": 8, "id": "2678c6e6", "metadata": {}, "outputs": [], "source": [ "car3 = Car(color='red', brand='Ferrari', year=2022, model='SF90')" ] }, { "cell_type": "code", "execution_count": 9, "id": "736d8cb5", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'E350'" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "car2.model" ] }, { "cell_type": "code", "execution_count": 10, "id": "e89cb24f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Ferrari'" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "car3.brand" ] }, { "cell_type": "code", "execution_count": 11, "id": "3d6fd290", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "car2.wheels" ] }, { "cell_type": "code", "execution_count": 12, "id": "95c98197", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "car3.wheels" ] }, { "cell_type": "markdown", "id": "c54ef9ba", "metadata": {}, "source": [ "\n", "\n", "## 12.5. Methods:" ] }, { "cell_type": "markdown", "id": "19803ebe", "metadata": {}, "source": [ "**Methods** are functions defined inside the body of a class. They are used to perform operations with the attributes of our objects. \n", "\n", "- **Defining Methods**: \n", "\n", "`class MyClass:\n", " def method_name(self, arg1, arg2):\n", " # Code for the method\n", " pass`" ] }, { "cell_type": "code", "execution_count": 13, "id": "93216183", "metadata": {}, "outputs": [], "source": [ "class Car:\n", " #Class-level attribute\n", " wheels = 4\n", "\n", " def __init__(self, brand, model, year, color):\n", " #Instance-level attributes\n", " self.brand = brand\n", " self.model = model\n", " self.year = year\n", " self.color = color\n", " \n", " #Methods\n", " def start(self):\n", " print(\"Starting the car...\")\n", "\n", " def stop(self):\n", " print(\"Stopping the car...\") " ] }, { "cell_type": "markdown", "id": "5a77dee8", "metadata": {}, "source": [ "In this example, Car has two methods: start and stop. These methods define the actions that a Car object can perform." ] }, { "cell_type": "markdown", "id": "bbb521cf", "metadata": {}, "source": [ "- **Calling Methods**: To call a method on an object, you use the `.` notation.\n", "\n", "`object_instance.method_name(arguments)`" ] }, { "cell_type": "code", "execution_count": 14, "id": "5b85c179", "metadata": {}, "outputs": [], "source": [ "car2 = Car('Mercedes-Benz', 'E350', 2023, 'silver')" ] }, { "cell_type": "code", "execution_count": 15, "id": "ca6b10ec", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Starting the car...\n" ] } ], "source": [ "car2.start()" ] }, { "cell_type": "code", "execution_count": 16, "id": "866d49c0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Stopping the car...\n" ] } ], "source": [ "car2.stop()" ] }, { "cell_type": "markdown", "id": "e67e8399", "metadata": {}, "source": [ "**Examples**:\n", "\n", "Q1. Create a Python class named Number with attributes for value and type. Implement methods to add, subtract, multiply, and divide two numbers. Then, create two instances of the Number class and perform arithmetic operations on them." ] }, { "cell_type": "code", "execution_count": 17, "id": "09aa41de", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Addition: 15\n", "Subtraction: 5\n", "Multiplication: 50\n", "Division: 2.0\n" ] } ], "source": [ "class Number:\n", " def __init__(self, value):\n", " self.value = value\n", "\n", " def add(self, other):\n", " return self.value + other.value\n", " \n", " def subtract(self, other):\n", " return self.value - other.value\n", "\n", " def multiply(self, other):\n", " return self.value * other.value\n", "\n", " def divide(self, other):\n", " if other.value == 0:\n", " raise ValueError(\"Cannot divide by zero\")\n", " return self.value / other.value\n", "\n", "num1 = Number(10)\n", "num2 = Number(5)\n", "\n", "result_add = num1.add(num2)\n", "result_subtract = num1.subtract(num2)\n", "result_multiply = num1.multiply(num2)\n", "result_divide = num1.divide(num2)\n", "\n", "print(\"Addition:\", result_add)\n", "print(\"Subtraction:\", result_subtract)\n", "print(\"Multiplication:\", result_multiply)\n", "print(\"Division:\", result_divide)" ] }, { "cell_type": "markdown", "id": "4f00e8fb", "metadata": {}, "source": [ "Q2. Create a Python class named Circle with an attribute for radius. Implement methods to calculate the area and circumference of the circle. Then, create an instance of the Circle class and calculate its area and circumference." ] }, { "cell_type": "code", "execution_count": 18, "id": "5b76bf86", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "radius: 5\n", "area : 78.53981633974483\n", "circumference : 31.41592653589793\n" ] } ], "source": [ "import math\n", "\n", "class Circle:\n", " \n", " def __init__(self, radius):\n", " self.radius = radius\n", " \n", " def area(self):\n", " return math.pi * (self.radius**2)\n", " \n", " def circumference(self):\n", " return 2 * math.pi * self.radius\n", " \n", "circle1 = Circle(5)\n", "print('radius:', circle1.radius)\n", "print('area :', circle1.area())\n", "print('circumference :', circle1.circumference())" ] }, { "cell_type": "markdown", "id": "d2d09a42", "metadata": {}, "source": [ "\n", "\n", "## 12.6. Inheritance:" ] }, { "cell_type": "markdown", "id": "93b12480", "metadata": {}, "source": [ "**Inheritance** is a feature in object-oriented programming that allows a class (child class) to inherit attributes and methods from another class (parent class). This promotes code reuse and helps to create hierarchical class structures. In Python, inheritance allows you to define a new class that is based on an existing class.\n", "\n", "Child classes inherit all the attributes and methods of their parent class.\n", "Child classes can override methods defined in the parent class to provide their own implementations.\n", "\n", "The syntax uses parentheses after the child class name to specify the parent class:\n", "\n", "`class ChildClass(ParentClass):\n", " # Attributes and methods of ChildClass`\n", " \n", "- ChildClass is the name of the new class you're creating.\n", "- ParentClass is the name of the existing class from which ChildClass inherits. " ] }, { "cell_type": "markdown", "id": "f6df2fa3", "metadata": {}, "source": [ "**`super()`** is a built-in function in Python that allows you to call methods or access attributes from the parent class. It is often used when overriding a method in a child class to still be able to call the parent class method and add additional functionality.\n", "\n", "`super().__init__(Parent Attributues)`" ] }, { "cell_type": "code", "execution_count": 19, "id": "3a075408", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Woof!\n", "Meow!\n" ] } ], "source": [ "class Animal:\n", " def __init__(self, name):\n", " self.name = name\n", "\n", " def make_sound(self):\n", " print(\"Generic animal sound\")\n", "\n", "class Dog(Animal):\n", " def make_sound(self): #Replaces the parent's make_sound() method with a dog-specific one\n", " print(\"Woof!\")\n", "\n", "class Cat(Animal):\n", " def make_sound(self): #Replaces the parent's make_sound() method with a cat-specific one\n", " print(\"Meow!\")\n", "\n", "dog = Dog(\"Buddy\")\n", "cat = Cat(\"Whiskers\")\n", "\n", "dog.make_sound()\n", "cat.make_sound()" ] }, { "cell_type": "code", "execution_count": 20, "id": "371529b2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Generic animal sound\n", "Woof!\n" ] } ], "source": [ "class Dog(Animal):\n", " def __init__(self, name, breed):\n", " super().__init__(name) #Calls the parent class's __init__()\n", " self.breed = breed #Adds additional attribute\n", "\n", " def make_sound(self):\n", " super().make_sound() #Optionally, call parent method\n", " print(\"Woof!\") #Add more behavior\n", "\n", "\n", "dog = Dog('brad', 'golden')\n", "dog.make_sound()" ] }, { "cell_type": "markdown", "id": "690ce23d", "metadata": {}, "source": [ "In this case, `super().__init__(name)` ensures that the name attribute is still set by the parent class's constructor, and super().make_sound() allows you to first use the parent’s make_sound() method before adding additional behavior.\n", "\n", "This example doesn't need super() because the child classes (Dog and Cat) do not override the parent class's `__init__()` method. By default, Python automatically calls the parent class’s `__init__()` if the child class doesn’t define its own `__init__()` method.\n", "\n", "In this case, Dog and Cat inherit the `__init__()` from Animal, so they correctly initialize the name attribute without needing `super()`. However, they override the make_sound() method, which is independent of the constructor. The Dog and Cat classes override the make_sound() method from the Animal class. However, this override doesn't need to use `super()` because the child classes do not need to call the parent class's make_sound() method. They are simply replacing it with their own implementation (i.e., Woof! for dogs and Meow! for cats). You only need `super()` if you want to extend or modify the functionality of a method in the parent class, not completely replace it." ] }, { "cell_type": "code", "execution_count": 21, "id": "22bf54c8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Brand: Toyota, Model: Corolla\n", "Seating Capacity: 5\n", "\n", "\n", "Brand: Ford, Model: F-150\n", "Payload Capacity: 3 tons\n" ] } ], "source": [ "#Base class\n", "class Vehicle:\n", " def __init__(self, brand, model):\n", " self.brand = brand\n", " self.model = model\n", "\n", " def display_info(self):\n", " print(f\"Brand: {self.brand}, Model: {self.model}\")\n", "\n", "#Child class (inherits from Vehicle)\n", "class Car(Vehicle):\n", " def __init__(self, brand, model, seating_capacity):\n", " #Call the parent class constructor using super()\n", " super().__init__(brand, model)\n", " self.seating_capacity = seating_capacity\n", "\n", " def display_info(self):\n", " super().display_info() #call the parent class method\n", " print(f\"Seating Capacity: {self.seating_capacity}\")\n", "\n", "#Another child class (inherits from Vehicle)\n", "class Truck(Vehicle):\n", " def __init__(self, brand, model, payload_capacity):\n", " super().__init__(brand, model)\n", " self.payload_capacity = payload_capacity\n", "\n", " def display_info(self):\n", " super().display_info() #Call the parent class method\n", " print(f\"Payload Capacity: {self.payload_capacity} tons\")\n", "\n", "\n", "my_car = Car(\"Toyota\", \"Corolla\", 5)\n", "my_truck = Truck(\"Ford\", \"F-150\", 3)\n", "\n", "my_car.display_info()\n", "print('\\n')\n", "my_truck.display_info()" ] }, { "cell_type": "markdown", "id": "7f7022a9", "metadata": {}, "source": [ "\n", "\n", "## 12.7. Polymorphism:" ] }, { "cell_type": "markdown", "id": "276353cc", "metadata": {}, "source": [ "It refers to the way in which different object classes can share the same method name, and those methods can be called from the same place even though a variety of different objects might be passed in." ] }, { "cell_type": "code", "execution_count": 22, "id": "ce663a1f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Daisy says Woof!\n", "Whiskey says Meow!\n" ] } ], "source": [ "class Dog:\n", " def __init__(self, name):\n", " self.name = name\n", "\n", " def speak(self):\n", " return self.name +' says Woof!'\n", " \n", "class Cat:\n", " def __init__(self, name):\n", " self.name = name\n", "\n", " def speak(self):\n", " return self.name +' says Meow!'\n", " \n", "Daisy = Dog('Daisy')\n", "Whiskey = Cat('Whiskey')\n", "\n", "print(Daisy.speak())\n", "print(Whiskey.speak())" ] }, { "cell_type": "code", "execution_count": 23, "id": "396f84e2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Daisy says Woof!\n", "Whiskey says Meow!\n" ] } ], "source": [ "for pet in [Daisy, Whiskey]:\n", " print(pet.speak())" ] }, { "cell_type": "markdown", "id": "b55c9381", "metadata": {}, "source": [ "\n", "\n", "## 12.8. Special Methods in OOP:" ] }, { "cell_type": "markdown", "id": "283b0a88", "metadata": {}, "source": [ "**Special methods**, also known as magic methods or dunder methods, are predefined methods in Python that start and end with double underscores `(__)`. They are used to define specific behaviors for objects of a class, such as how they are represented as strings, how their length is calculated, and how they are deleted." ] }, { "cell_type": "markdown", "id": "752535ad", "metadata": {}, "source": [ "- **`__str__(self)`**: This method is called when you attempt to convert an object into a string (e.g., when you use `print()`)." ] }, { "cell_type": "code", "execution_count": 24, "id": "3b1caac4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Person (John, 30)\n" ] } ], "source": [ "class Person:\n", " def __init__(self, name, age):\n", " self.name = name\n", " self.age = age\n", "\n", " def __str__(self):\n", " return f'Person ({self.name}, {self.age})'\n", "\n", "p = Person('John', 30)\n", "print(p) #Calls __str__()" ] }, { "cell_type": "markdown", "id": "3b3137aa", "metadata": {}, "source": [ "- **`__len__(self)`**: This method is called when you use the built-in `len()` function on an object. It should return an integer representing the length or size of the object." ] }, { "cell_type": "code", "execution_count": 25, "id": "b21c068f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['Book1', 'Book2', 'Book3']\n", "3\n" ] } ], "source": [ "class BookCollection:\n", " def __init__(self, books):\n", " self.books = books\n", "\n", " def __len__(self):\n", " return len(self.books)\n", "\n", "collection = BookCollection([\"Book1\", \"Book2\", \"Book3\"])\n", "print(collection.books)\n", "print(len(collection)) #Calls __len__()" ] }, { "cell_type": "markdown", "id": "dab254ef", "metadata": {}, "source": [ "- **`__del__(self)`**: This method defines what happens when the object is deleted or garbage collected." ] }, { "cell_type": "code", "execution_count": 26, "id": "e95607e3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "book1 is deleted.\n" ] } ], "source": [ "class BookCollection:\n", " def __init__(self, books):\n", " self.books = books\n", "\n", " def __del__(self):\n", " print(f'{self.books} is deleted.')\n", " \n", "book1 = BookCollection('book1')\n", "del book1 #Calls __del__()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "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.9.13" } }, "nbformat": 4, "nbformat_minor": 5 }