Class

A class is a template that defines the characteristics of a real world entity. It describes all the details of the entity like its data and functionality.

A class is something singular and should have a singular name.

Valid class names are:  Student, Rectangle, Employee etc.

Improper class names are: Students, _007JamesBond, Students_of_school etc.

A class can be defined like this:

class Class_name:
    statements..

A class have two type of members:

  1. Class members
    Class members belongs to class and has nothing to do with objects.
    Class methods can be directly accessed by class name and again no object is required. They are used to perform calculation that is common to all objects of the class.
    Class member variables are created only once when class is loaded in memory. Their values are shared among objects of the class.
    If you have experience with Java or C#, we use static keyword for such methods and variables.
  2. Instance members
    Instance members are those which belongs to instances(objects). So, instance variables are created only when the instances of class are created.
    For example, if we have two variables in a class named name and age, and we create 5 objects of this class, then there will be 10 variables in total, 2 for each object.
    Instance member variables are accessed with the help of self keyword. It is the reference of current object inside the class.
    If you are familiar with languages like C++, Java, C# etc. self is same as this.

Here are some examples of classes:

Class to define Square

class Square:

    def area(self):                # each method will have self as the first parameter
        return self.side*self.side
    
    def perimeter(self):
        return 4*self.side

The variable side will automatically be created as an instance member

Class to define Rectangle

class Rectangle:

    def area(self):
        return self.length*self.width
    
    def perimeter(self):
        return 2*(self.length+self.width)

Class to demonstrate class methods

class Data:
    @classmethod
    def square(cls, x):
        return x*x

    @staticmethod
    def cube(x):
        return x*x*x

print(Data.square(5))
print(Data.cube(5))

Both @classmethod and @staticmethod are similar with one difference. If you are using @classmethod, you need to specify class reference as the first parameter (cls). Here cls will contain current class information which will be automatically provided by Python.

Object

An object is the physical representation of a class. It exists in the real world and can be distinguished from other objects of the same type. Everything you see around are ojbects.

An object in Python can be created like this:

Syntax:

object_name = Class_name()

Example:

rect1 = Rectangle()
rect2 = Rectangle()

Complete example that uses class and object

In this example, we will create multiple rectangle objects and sum their area

class Rectangle:

    def set_side(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length*self.width

    def perimeter(self):
        return 2*(self.length+self.width)


rect1 = Rectangle()
rect2 = Rectangle()
rect3 = Rectangle()

rect1.set_side(4,6)
rect2.set_side(3,5)
rect3.set_side(2,7)

sum_of_areas = rect1.area() + rect2.area() + rect3.area()
sum_of_perimeters = rect1.perimeter() + rect2.perimeter() + rect3.perimeter()

print("Sum of area of rectangles is " , sum_of_areas)
print("Sum of perimeter of rectangles is " , sum_of_perimeters)

Constructor

A constructor is a method that is called automatically when object of a class is created. Since, it is called only once, we can write any logic here that we want to execute only once.

A constructor can be added to the class with following syntax:

def __init__(self, parameter1=default1, parameter2=default2):
    statements..

Here is an example:

class Square:

    counter = 0          # class variable, shared between multiple objects
    def __init__(self, side=0):
        self.side = side
        Square.counter += 1

    def area(self):
        return self.side * self.side

    def perimeter(self):
        return 4 * self.side

sq1 = Square()          # side = 0
sq2 = Square(5)         # side = 5

print("Area of first square is " , sq1.area())
print("Area of second square is " , sq2.area())
print("Total squares created " , Square.counter)

Python don’t have overloaded constructors. Instead, there is only one constructor and we can specify default values in case nothing is passed for particular parameters.

Destructor

There is not a real destructor. The __del__ method is something similar which is called when object is about to be destroyed.

Here is the syntax:

def __del__(self):
    statements..

Here is an example:

class Square:

    def __init__(self, side=0):
        self.side = side

    def __del__(self):
        del self.side

    def area(self):
        return self.side * self.side

    def perimeter(self):
        return 4 * self.side

sq1 = Square()          # side = 0
sq2 = Square(5)

print("Area of first square is " , sq1.area())
print("Area of second square is " , sq2.area())

Inheritance

Inheritance is the feature of OOPS that allows us to create new specialized classes from existing classes. This helps in extending the capabilities of existing classes. Existing class here is called base class and the newly created class is called subclass.

Let us assume that we have a class named Employee. Now, we want to create new classes named Manager and Accountant. The Employee class already have some basic functionality and data. Instead of duplicating the functionality and data again in Manager and Accountant class, we can inherit features of Employee in both Manager and Accountant. Now, we can concentrate on the logic of these new classes only.

Inheritance is also called is a relationship. This can be clearly seen that Manager is Employee as well as Accountant is Employee.

Here is an example of a basic inheritance:

class Animal:
    def walk_style(self):
        print("Walks on 4 legs")

class Dog(Animal):
    def animal_type(self):
        print("Dog is wild and domestic")

class Zebra(Animal):
    def animal_type(self):
        print("Zebra is wild")

d = Dog()
z = Zebra()

d.animal_type()
d.walk_style()

z.animal_type()
z.walk_style()

Constructors in Inheritance

Here is the updated example:

class Animal:

    def __init__(self, animal_name):
        self.animal_name = animal_name
        print(animal_name , " initialized in base")

    def walk_style(self):
        print(self.animal_name , " walks on 4 legs")

class Dog(Animal):

    def __init__(self, animal_name):
        Animal.__init__(self, animal_name)      # pass animal name to base class
        self.animal_name = animal_name
        print(animal_name , " initialized in subclass")

    def animal_type(self):
        print(self.animal_name , " is wild and domestic")

class Zebra(Animal):

    def __init__(self, animal_name):
        Animal.__init__(self, animal_name)
        self.animal_name = animal_name
        print(animal_name , " initialized in subclass")

    def animal_type(self):
        print(self.animal_name , " is wild")

d = Dog("Dog")
z = Zebra("Zebra")

d.animal_type()
d.walk_style()

z.animal_type()
z.walk_style()

Your output should be similar to this-

Dog initialized in base
Dog initialized in subclass
Zebra initialized in base
Zebra initialized in subclass
Dog is wild and domestic
Dog walks on 4 legs
Zebra is wild
Zebra walks on 4 legs

You can see that constructors are executed from top (base class) to bottom (sub class).

Method Overriding

Method overriding is the concept of updating existing base class functionality in sub class. In programming, we create base class methods again in sub class which allow us to hide base functionality.

Let us assume we want to create one more class Penguin like this:

class Penguin(Animal):

    def __init__(self, animal_name):
        Animal.__init__(self, animal_name)
        self.animal_name = animal_name

    def animal_type(self):
        print(self.animal_name , " is wild")

The problem here is that Animal’s walk_style will show that Penguin walks on 4 legs which is incorrect because penguins walks on 2 legs. To hide base class walk_style() for Penguin, we will create the same method again in the class Penguin. Here is an example:

class Animal:

    def __init__(self, animal_name):
        self.animal_name = animal_name
        print(animal_name , " initialized in base")

    def walk_style(self):
        print(self.animal_name , " walks on 4 legs")

class Dog(Animal):

    def __init__(self, animal_name):
        Animal.__init__(self, animal_name)
        self.animal_name = animal_name

    def animal_type(self):
        print(self.animal_name , " is wild and domestic")

class Zebra(Animal):

    def __init__(self, animal_name):
        Animal.__init__(self, animal_name)
        self.animal_name = animal_name

    def animal_type(self):
        print(self.animal_name , " is wild")

class Penguin(Animal):

    def __init__(self, animal_name):
        Animal.__init__(self, animal_name)
        self.animal_name = animal_name

    def animal_type(self):
        print(self.animal_name , " is wild")

    def walk_style(self):
        print(self.animal_name , " walks on 2 legs")

d = Dog("Dog")
z = Zebra("Zebra")
p = Penguin("Penguin")

d.animal_type()
d.walk_style()

z.animal_type()
z.walk_style()

p.animal_type()
p.walk_style()