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.
We'll learn about the following topics:
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).
In Python, everything is an object. Numbers, strings, lists, dictionaries, and even functions are all objects.
print(type(1))
print(type([]))
print(type(()))
print(type({}))
print(type({1,}))
print(type('1'))
print(type(all))
<class 'int'> <class 'list'> <class 'tuple'> <class 'dict'> <class 'set'> <class 'str'> <class 'builtin_function_or_method'>
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.
class ClassName: Attributes and Methods
class Car:
pass
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.
ClassName()
car1 = Car()
print(type(car1))
<class '__main__.Car'>
Attributes are the properties or characteristics of an object. For example, a car object might have attributes like color, brand, and model.
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.
class ClassName: attribute1 = value1 # Class level attribute attribute2 = value2 # Another class level attribute
class Car:
#Class-level attribute
wheels = 4
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__)
.
class ClassName: def __init__(self, argument1, argument2): self.attribute1 = argument1 # Instance-level attribute self.attribute2 = argument2 # Another instance level attribute
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.
class ClassName: def __init__(self, argument1=default_value): self.attribute1 = argument1
class Car:
#Class-level attribute
wheels = 4
def __init__(self, brand, model, year, color):
#Instance-level attributes
self.brand = brand
self.model = model
self.year = year
self.color = color
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.
.
notation.object_instance.attribute_name
let's create an instance from Car class. Here are the two ways to pass arguments to a function in Python:
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__)
.
Arguments are passed by name, using the syntax argument_name=value
. The order of keyword arguments doesn't matter.
car2 = Car('Mercedes-Benz', 'E350', 2023, 'silver')
car3 = Car(color='red', brand='Ferrari', year=2022, model='SF90')
car2.model
'E350'
car3.brand
'Ferrari'
car2.wheels
4
car3.wheels
4
Methods are functions defined inside the body of a class. They are used to perform operations with the attributes of our objects.
class MyClass: def method_name(self, arg1, arg2): # Code for the method pass
class Car:
#Class-level attribute
wheels = 4
def __init__(self, brand, model, year, color):
#Instance-level attributes
self.brand = brand
self.model = model
self.year = year
self.color = color
#Methods
def start(self):
print("Starting the car...")
def stop(self):
print("Stopping the car...")
In this example, Car has two methods: start and stop. These methods define the actions that a Car object can perform.
.
notation.object_instance.method_name(arguments)
car2 = Car('Mercedes-Benz', 'E350', 2023, 'silver')
car2.start()
Starting the car...
car2.stop()
Stopping the car...
Examples:
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.
class Number:
def __init__(self, value):
self.value = value
def add(self, other):
return self.value + other.value
def subtract(self, other):
return self.value - other.value
def multiply(self, other):
return self.value * other.value
def divide(self, other):
if other.value == 0:
raise ValueError("Cannot divide by zero")
return self.value / other.value
num1 = Number(10)
num2 = Number(5)
result_add = num1.add(num2)
result_subtract = num1.subtract(num2)
result_multiply = num1.multiply(num2)
result_divide = num1.divide(num2)
print("Addition:", result_add)
print("Subtraction:", result_subtract)
print("Multiplication:", result_multiply)
print("Division:", result_divide)
Addition: 15 Subtraction: 5 Multiplication: 50 Division: 2.0
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.
import math
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * (self.radius**2)
def circumference(self):
return 2 * math.pi * self.radius
circle1 = Circle(5)
print('radius:', circle1.radius)
print('area :', circle1.area())
print('circumference :', circle1.circumference())
radius: 5 area : 78.53981633974483 circumference : 31.41592653589793
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.
Child classes inherit all the attributes and methods of their parent class. Child classes can override methods defined in the parent class to provide their own implementations.
The syntax uses parentheses after the child class name to specify the parent class:
class ChildClass(ParentClass): # Attributes and methods of ChildClass
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.
super().__init__(Parent Attributues)
class Animal:
def __init__(self, name):
self.name = name
def make_sound(self):
print("Generic animal sound")
class Dog(Animal):
def make_sound(self): #Replaces the parent's make_sound() method with a dog-specific one
print("Woof!")
class Cat(Animal):
def make_sound(self): #Replaces the parent's make_sound() method with a cat-specific one
print("Meow!")
dog = Dog("Buddy")
cat = Cat("Whiskers")
dog.make_sound()
cat.make_sound()
Woof! Meow!
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) #Calls the parent class's __init__()
self.breed = breed #Adds additional attribute
def make_sound(self):
super().make_sound() #Optionally, call parent method
print("Woof!") #Add more behavior
dog = Dog('brad', 'golden')
dog.make_sound()
Generic animal sound Woof!
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.
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.
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.
#Base class
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def display_info(self):
print(f"Brand: {self.brand}, Model: {self.model}")
#Child class (inherits from Vehicle)
class Car(Vehicle):
def __init__(self, brand, model, seating_capacity):
#Call the parent class constructor using super()
super().__init__(brand, model)
self.seating_capacity = seating_capacity
def display_info(self):
super().display_info() #call the parent class method
print(f"Seating Capacity: {self.seating_capacity}")
#Another child class (inherits from Vehicle)
class Truck(Vehicle):
def __init__(self, brand, model, payload_capacity):
super().__init__(brand, model)
self.payload_capacity = payload_capacity
def display_info(self):
super().display_info() #Call the parent class method
print(f"Payload Capacity: {self.payload_capacity} tons")
my_car = Car("Toyota", "Corolla", 5)
my_truck = Truck("Ford", "F-150", 3)
my_car.display_info()
print('\n')
my_truck.display_info()
Brand: Toyota, Model: Corolla Seating Capacity: 5 Brand: Ford, Model: F-150 Payload Capacity: 3 tons
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.
class Dog:
def __init__(self, name):
self.name = name
def speak(self):
return self.name +' says Woof!'
class Cat:
def __init__(self, name):
self.name = name
def speak(self):
return self.name +' says Meow!'
Daisy = Dog('Daisy')
Whiskey = Cat('Whiskey')
print(Daisy.speak())
print(Whiskey.speak())
Daisy says Woof! Whiskey says Meow!
for pet in [Daisy, Whiskey]:
print(pet.speak())
Daisy says Woof! Whiskey says Meow!
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.
__str__(self)
: This method is called when you attempt to convert an object into a string (e.g., when you use print()
).class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f'Person ({self.name}, {self.age})'
p = Person('John', 30)
print(p) #Calls __str__()
Person (John, 30)
__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.class BookCollection:
def __init__(self, books):
self.books = books
def __len__(self):
return len(self.books)
collection = BookCollection(["Book1", "Book2", "Book3"])
print(collection.books)
print(len(collection)) #Calls __len__()
['Book1', 'Book2', 'Book3'] 3
__del__(self)
: This method defines what happens when the object is deleted or garbage collected.class BookCollection:
def __init__(self, books):
self.books = books
def __del__(self):
print(f'{self.books} is deleted.')
book1 = BookCollection('book1')
del book1 #Calls __del__()
book1 is deleted.