Python Classes and Objects
Think of a class as a blueprint of a house. It contains all the details about the floors, doors, windows, etc.
Based on these descriptions, we build the house. The actual physical house is the object.
 
    class Student:
    pass
student1 = Student()
student2 = Student()Here, student1 and student2 are objects of the Student class.
Now, we can start adding different attributes to these object instances.
class Student:
    pass
# create an object of Student
student1 = Student()
# initialize object attributes
student1.name = 'Harry'
student1.marks = 85
# print object attributes
print(student1.name)
print(student1.marks)Output
Harry 85
Let's now see how we can define methods inside a class.
class Student:
    # method to check if student passed or failed
    def check_pass_fail(self):
        if self.marks >= 40:
            return True
        else:
            return False
# instantiate Student class
student1 = Student()
# initialize data attributes of student1
student1.name = 'Harry'
student1.marks = 85
# call the check_pass_fail() method for student1
did_pass = student1.check_pass_fail()
# print the result
print(did_pass)Output
True
Here, the check_pass_fail() method is defined inside the Student class. Now, any object
    created from the Student class can access this method.
We have called this method without passing any arguments. However, the method definition takes one argument named
    self.
def check_pass_fail(self):
    if self.marks >= 40:
        return True
    else:
        return FalseWhenever we define methods for a class, we need to use self as the first parameter. This
    self represents the object calling it.
In our example, self refers to the student1 object and self.marks refers to the
    marks attribute of student1.
Let's try the same for another Student object.
class Student:
    def check_pass_fail(self):
        if self.marks >= 40:
            return True
        else:
            return False
student1 = Student()
student1.name = 'Harry'
student1.marks = 85
did_pass = student1.check_pass_fail()
print(did_pass)
student2 = Student()
student2.name = 'Janet'
student2.marks = 30
did_pass = student2.check_pass_fail()
print(did_pass)Output
True False
 
    Note: Using self is just a convention, but we highly recommend using
    it.
The __init__() Method
Adding attributes to the object manually after defining it is not a good practice.
Python offers a much more elegant and compact way of defining attributes right while instantiating the object. For
    that, we use the init method.
If you are coming from other languages like C++ or Java, the Python __init__() method closely resembles
    constructors.
class Student:
    
    def __init__(self, name, marks):
        self.name = name
        self.marks = marks
        
    def check_pass_fail(self):
        if self.marks >= 40:
            return True
        else:
            return False
student1 = Student('Harry', 85)
print(student1.name)
print(student1.marks)Output
Harry 85
When we create an object, this __init__() method is called automatically. When creating the
    student1 object, we have passed Harry and 85 to its name and
    marks attributes in the __init()__ method.
 
    Remember, the first parameter self represents the object calling it. In contrast, the second and third
    parameters take the two arguments which we passed during object creation.
Now, for the student1 object, the name attribute will be Harry, and the
    marks attribute will be 85.
Let's try creating one more object.
class Student:
    
    def __init__(self, name, marks):
        self.name = name
        self.marks = marks
        
    def check_pass_fail(self):
        if self.marks >= 40:
            return True
        else:
            return False
student1 = Student('Harry', 85)
print(student1.name)
print(student1.marks)
student2 = Student('Janet', 30)
print(student2.name)
print(student2.marks)Output
Harry 85 Janet 30
The table below shows the attributes of student1 and student2.
| Object | name | marks | 
|---|---|---|
| student1 | Harry | 85 | 
| student2 | Janet | 30 | 
Let's check if they passed or failed the exams using the check_pass_fail() method.
class Student:
    
    def __init__(self, name, marks):
        self.name = name
        self.marks = marks
        
    def check_pass_fail(self):
        if self.marks >= 40:
            return True
        else:
            return False
student1 = Student('Harry', 85)
did_pass = student1.check_pass_fail()
print(did_pass)
student2 = Student('Janet', 30)
did_pass = student2.check_pass_fail()
print(did_pass)Output
True False
| Object | marks | marks >= 40 | 
|---|---|---|
| student1 | 85 | True | 
| student2 | 30 | False | 
Example: Add Two Complex Numbers
In this example, we will add two complex numbers manually. Python already handles this by default, but we will create
    our own Complex class to better understand the concepts of object-oriented programming.
If you do not know, a complex number has real and imaginary parts. When we add two complex numbers, we need to add real and imaginary parts separately.
Let's first create a class that represents complex numbers.
class Complex:
    def __init__(self, real, imag):
        self.real = real
        self.imag = imag
n1 = Complex(5, 6)
n2 = Complex(-4, 2)Now, let's create a method to add these complex numbers.
class Complex:
    def __init__(self, real, imag):
        self.real = real
        self.imag = imag
        
    def add(self, number):
        real = self.real + number.real
        imag = self.imag + number.imag
        sum = Complex(real, imag)
        return sum
n1 = Complex(5, 6)
n2 = Complex(-4, 2)
result = n1.add(n2)The value returned by add() is itself an object of the Complex class.
Let's print the attributes of the result object.
class Complex:
    def __init__(self, real, imag):
        self.real = real
        self.imag = imag
        
    def add(self, number):
        real = self.real + number.real
        imag = self.imag + number.imag
        sum = Complex(real, imag)
        return sum
n1 = Complex(5, 6)
n2 = Complex(-4, 2)
result = n1.add(n2)
print('real =', result.real)
print('imag =', result.imag)Output
real = 1 imag = 8
Here, the n1 object is calling the add() method by passing n2 as an argument. The return value of the complex addition is then stored in the result object.
Why object-oriented programming?
Creating objects allows us to organize related data and functionalities together. This helps us to write structured and flexible code.
Now, instead of thinking in terms of individual data and functions, we start thinking in terms of objects and how one object interacts with the other. This helps us to divide a complex problem into smaller sub-problems.
Also, using an object-oriented style of programming makes our code reusable because we can define multiple objects with similar attributes and functionalities from a single class.
Programming Task
- Create a class named Triangle.
- Create an object from it. The object will have three attributes named a, b, and c that represent the sides of the triangle.
- The Triangleclass will have two methods:- The __init__()method to initialize the sides.
- A method to calculate the perimeter of the triangle from its sides.
 
- The 
- The perimeter of the triangle should be printed from outside the class.
Here's the barebone for this program:
cclass Triangle:
    def __init__(self, a, b, c):
        # write code here
        pass
    
    # write code here
    
t1 = Triangle(3, 4, 5)
# write code hereSolution
class Triangle:
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c
    
    def get_perimeter(self):
        perimeter = self.a + self.b + self.c
        return perimeter
    
t1 = Triangle(3, 4, 5)
perimeter = t1.get_perimeter()
print('The perimeter of the t1 triangle is', perimeter)Output
The perimeter of the t1 triangle is 12