Class and Objects in Python
Class and Objects in Python
Suppose you wish to store the number of books you have, you can simply do that by
using a variable. Or, say you want to calculate the sum of 5 numbers and store it in a
variable, well, that can be done too!
Primitive data structures like numbers, strings, and lists are designed to store simple
values in a variable. Suppose, your name, or square of a number, or count of some
marbles (say).
But what if you need to store the details of all the Employees in your company? For
example, you may try to store every employee in a list, you may later be confused about
which index of the list represents what details of the employee(e.g. which is the name
field, or the empID etc.)
Example:
employee1 = ['John Smith', 104120, "Developer", "Dept. 2A"]
employee2 = ['Mark Reeves', 211240, "Database Designer", "Dept. 11B"]
employee3 = ['Steward Jobs', 131124, "Manager", "Dept. 2A"]
Even if you try to store them in a dictionary, after an extent, the whole codebase will be
too complex to handle. So, in these scenarios, we use Classes in python.
A class is used to create user-defined data structures in Python. Classes define functions,
which are termed methods, that describe the behaviors and actions that an object created
from a class can perform. OOPS concepts in Python majorly deal with classes and
objects.
Classes make the code more manageable by avoiding complex codebases. It does so, by
creating a blueprint or a design of how anything should be defined. It defines what
properties or functions, any object which is derived from the class should have.
IMPORTANT:
A class just defines the structure of how anything should look. It does not point to
anything or anyone in particular. For example, say, HUMAN is a class, which has
suppose -- name, age, gender, city. It does not point to any specific HUMAN out there,
but yes, it explains the properties and functions any HUMAN should or any object of
class HUMAN should have.
An instance of a class is called the object. It is the implementation of the class and exists
in real.
An object is a collection of data (variables) and methods (functions) that access the data.
It is the real implementation of a class.
Consider this example, here Human is a class - It is just a blueprint that defines
how Human should be, and not a real implementation. You may say that "Human" class
just exists logically.
However, "Ron" is an object of the Human class (please refer to the image given above
for understanding). That means, Ron is created by using the blueprint of the Human class,
and it contains the real data. "Ron" exists physically, unlike "Human" (which just exists
logically). He exists in real, and implements all the properties of the class Human, such
as, Ron have a name, he is 15 years old, he is a male, and lives in Delhi. Also, Ron
implements all the methods of Human class, suppose, Ron can walk, speak, eat, and
sleep.
And many humans can be created using the blueprint of class Human. Such as, we may
create 1000s of more humans by referring to the blueprint of the class Human, using
objects.
Quick Tip:
class = blueprint(suppose an architectural drawing). The Object is an actual thing that is
built based on the ‘blueprint’ (suppose a house). An instance is a virtual copy (but not a
real copy) of the object.
When a class is defined, only the blueprint of the object is created, and no memory is
allocated to the class. Memory allocation occurs only when the object or instance is
created. The object or instance contains real data or information.
How to Define a Class in Python?
Classes in Python can be defined by the keyword class, which is followed by the name of
the class and a colon.
Syntax:
class Human:
pass
Indented code below the class definition is considered part of the class body.
'pass' is commonly used as a placeholder, in the place of code whose implementation we
may skip for the time being. "pass" allows us to run the code without throwing an error in
Python.
What is an _init_ Method?
The properties that all Human objects must have been defined in a method called init().
Every time a new Human object is created, __init__() sets the initial state of the object by
assigning the values we provide inside the object’s properties. That
is, __init__() initializes each new instance of the class.
__init__() can take any number of parameters, but the first parameter is always a variable
called self.
The self parameter is a reference to the current instance of the class. It means,
the self parameter points to the address of the current object of a class, allowing us to
access the data of its(the object's) variables.
So, even if we have 1000 instances (objects) of a class, we can always get each of their
individual data due to this self because it will point to the address of that particular object
and return the respective value.
Note:
We can use any name in place of self, but it has to be the first parameter of any function
in the class.
Let us see, how to define __init__() in the Human class:
Code:
class Human:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
In the body of .__init__(), we are using the self variable 3 times, for the following:
self.name = 'name' creates an attribute called name and assigns to it the value of the name
parameter.
self.age = age attribute is created and assigned to the value of age parameter passed.
self.gender = gender attribute is created and assigned to the value of gender parameter
passed.
There are 2 types of attributes in Python:
1. Class Attribute:
These are the variables that are the same for all instances of the class. They do not have
new values for each new instance created. They are defined just below the class
definition.
Code:
class Human:
#class attribute
species = "Homo Sapiens"
Here, the species will have a fixed value for any object we create.
2. Instance Attribute:
Instance attributes are the variables that are defined inside of any function in class.
Instance attributes have different values for every instance of the class. These values
depend upon the value we pass while creating the instance.
Code:
class Human:
#class attribute
species = "Homo Sapiens"
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
Here, name, age, and gender are the instance attributes. They will have different values
for new instances of the class.
For properties that should have a similar value per instance of a class, use class attributes.
For properties that differ per instance, use instance attributes.
Creating an Object in Class
When we create a new object from a class, it is called instantiating an object. An object
can be instantiated by the class name followed by the parentheses. We can assign the
object of a class to any variable.
Syntax:
x = ClassName()
As soon as an object is instantiated, memory is allocated to them. So, if we compare 2
instances of the same class using '==', it will return false(because both will have different
memory assigned).
Suppose, we try to create objects of our Human class, then we also need to pass the
values for name, age, and gender.
Code:
class Human:
#class attribute
species = "Homo Sapiens"
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
print(x.species) # species are class attributes, hence will have same value for all
instances
print(y.species)
# name, gender and age will have different values per instance, because they are instance
attributes
print(f"Hi! My name is {x.name}. I am a {x.gender}, and I am {x.age} years old")
print(f"Hi! My name is {y.name}. I am a {y.gender}, and I am {y.age} years old")
Output:
Homo Sapiens
Homo Sapiens
Hi! My name is Ron. I am a male, and I am 15 years old
Hi! My name is Miley. I am a female, and I am 22 years old
In the above example, we have our class attributes values same "Homo Sapiens", but the
instance attributes values are different as per the value we passed while creating our
object.
However, we can change the value of class attributes, by
assigning classname.classAttribute with any new value.
Code:
class Human:
#class attribute
species = "Homo Sapiens"
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
Human.species = "Sapiens"
obj = Human("Brek",11,"male")
print(obj.species)
Output:
Sapiens
Instance Methods
An instance method is a function defined within a class that can be called only from
instances of that class. Like init(), an instance method's first parameter is always self.
Let's take an example and implement some functions can class Human can perform --
Code:
class Human:
#class attribute
species = "Homo Sapiens"
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
#Instance Method
def speak(self):
return f"Hello everyone! I am {self.name}"
#Instance Method
def eat(self, favouriteDish):
return f"I love to eat {favouriteDish}!!!"
x = Human("Ciri",18,"female")
print(x.speak())
print(x.eat("momos"))
Output:
Hello everyone! I am Ciri
I love to eat momos!!!
This Human class has two instance methods:
speak(): It returns a string displaying the name of the Human.
eat(): It has one parameter "favouriteDish" and returns a string displaying the favorite
dish of the Human.
Having gained a thorough knowledge of what Python classes, objects, and methods are, it
is time for us to turn our focus toward the OOP core principles, upon which it is built.
Fundamentals of OOPS in Python
There are four fundamental concepts of Object-oriented programming –
Inheritance
Encapsulation
Polymorphism
Data abstraction
Let us now look into each of the OOPS concepts in Python deeply.
Inheritance
People often say to newborn babies that they have got similar facial features to their
parents, or that they have inherited certain features from their parents. It is likely that you
too have noticed that you have inherited some or the other features from your parents.
Inheritance too is very similar to the real-life scenario. But here, the "child classes"
inherit features from their "parent classes." And the features they inherit here are termed
as "properties" and "methods"!
Inheritance is the process by which a class can inherit or derive the properties(or data)
and methods(or functions) of another class. Simply, the process of inheriting the
properties of a parent class into a child class is known as inheritance.
The class whose properties are inherited is the Parent class, and the class that inherits the
properties from the Parent class is the Child class.
Let us see the syntax of inheritance in Python:
Code:
class parent_class:
#body of parent class
def description(self):
print(f"Hey! My name is {self.name}, I'm a {self.gender} and I'm {self.age} years
old")
class Girl(Human):
def schoolName(self,schoolName):
print("I study in {schoolName}")
def dance(self):
print("I can dance")
class Girl(Human):
def dance(self):
print("I can do classic dance")
def activity(self):
super().dance()
g = Girl('Lily', 20, 'girl')
g.description()
g.activity()
Output:
Hey! My name is Lily, I'm a girl and I'm 20 years old
I can dance
Here, we have defined the dance() method in Human and Girl classes. But, both methods
have different implementations as you can see. In Human class, the dance method says "I
can dance", whereas in Girl class, the dance method says "I can do classical dance". So,
let us call the parent class's dance method from the child class.
You can see, in line 19, we are calling the dance method using super().dance(). This will
call the dance method from the Human class. So, it prints "I can dance". Although, there
was already an implementation for dance() in Girl(at line 15).
When we call any method using super(), the method in the superclass will be called even
if there is a method with the same name in the subclass.
Polymorphism
Suppose, you are scrolling through your Instagram feeds on your phone. You suddenly
felt like listening to some music as well, so you opened Spotify and started playing your
favorite song. Then, after a while, you got a call, so you paused all the background
activities you were doing, to answer it. It was your friend's call, asking you to text the
phone number of some person. So, you messaged him the number, and resumed your
activities.
Did you notice one thing? You could scroll through feeds, listen to music, attend/make
phone calls, message -- everything just with a single device - your Mobile Phone! Whoa!
So, Polymorphism is something similar to that. 'Poly' means multiple and 'morph' means
forms. So, polymorphism altogether means something that has multiple forms. Or, 'some
thing' that can have multiple behaviours depending upon the situation.
Polymorphism in OOPS refers to the functions having the same names but carrying
different functionalities. Or, having the same function name, but different function
signature(parameters passed to the function).
A child class inherits all properties from its parent class methods. But sometimes, it wants
to add its own implementation to the methods. There are ample of ways we can
use polymorphism in Python.
Example of Inbuilt Polymorphic Functions
len() is an example of inbuilt polymorphic function. Because, we can use it to calclate the
length of vaious types like string, list, tuple or dictionary, it will just compute the result
and return.
Code:
print(len('deepa'))
print(len([1,2,5,9]))
print(len({'1':'apple','2':'cherry','3':'banana'}))
Output:
5
4
3
Here we have passed a string, list and dictionary to the len function and it computed the
result. So, it is an example of an inbuilt Polymorphic function.
We also have polymorphism with the '+' addition operator. We can use it to 'add' integers
or floats or any arithmetic addition operation. In the other hand, with String, it performs
the 'concatenation' operation.
Code:
x=4+5
y = 'python' + ' programming'
z = 2.5 + 3
print(x)
print(y)
print(z)
Output:
9
python programming
5.5
So, we can see that a single operator '+' has been used to carry out different operations for
distinct data types.
Polymorphism with Class Methods
We can perform polymorphism with the class methods. Let's see how:
Code:
class Monkey:
def color(self):
print("The monkey is yellow coloured!")
def eats(self):
print("The monkey eats bananas!")
class Rabbit:
def color(self):
print("The rabbit is white coloured!")
def eats(self):
print("The rabbit eats carrots!")
mon = Monkey()
rab = Rabbit()
for animal in (mon, rab):
animal.color()
animal.eats()
Output:
The monkey is yellow coloured!
The monkey eats bananas!
The rabbit is white coloured!
The rabbit eats carrots!
Here, we can iterate over the objects of Monkey & Rabbit using one variable - animal,
and it can call the instance methods of both of them. So, here one variable animal is used
to represent the behaviour (color() & eats()) of Monkey as well as Rabbit. So, it is
following the rules of Polymorphism!
Polymorphism with Inheritance
We can have polymorphism with inheritance as well. It is possible to modify a method in
a child class that it has inherited from the parent class, adding its own implementation to
the method. This process of re-implementing a method in the child class is known
as Method Overriding in Python. Here is an example that shows polymorphism with an
inheritance:
Code:
class Shape:
def no_of_sides(self):
pass
def two_dimensional(self):
print("I am a 2D object. I am from shape class")
class Square(Shape):
def no_of_sides(self):
print("I have 4 sides. I am from Square class")
class Triangle(Shape):
def no_of_sides(self):
print("I have 3 sides. I am from Triangle class")
The process of binding data and corresponding methods (behavior) together into a single
unit is called encapsulation in Python.
In other words, encapsulation is a programming technique that binds the class members
(variables and methods) together and prevents them from being accessed by other classes.
It is one of the concepts of OOPS in Python.
Encapsulation is a way to ensure security. It hides the data from the access of outsiders.
An organization can protect its object/information against unwanted access by clients or
any unauthorized person by encapsulating it.
Getters and Setters
We mainly use encapsulation for Data Hiding. We do so by
defining getter and setter methods for our classes. If anyone wants some data, they can
only get it by calling the getter method. And, if they want to set some value to the data,
they must use the setter method for that, otherwise, they won't be able to do the same. But
internally, how these getter and setter methods are performed remains hidden from the
outside world.
Code of getter & setter in Encapsulation
class Library:
def __init__(self, id, name):
self.bookId = id
self.bookName = name
def getSalary(self):
print(f"The salary of Employee is {self.__salary}")
def getSalary(self):
print(f"The salary of Employee is {self.__salary}")
employee1 = Employee("John Gates", 110514, "$1500")
class Bike(Vehicle):
def no_of_wheels(self): # provide definition for abstract method
print("Bike have 2 wheels")
class Tempo(Vehicle):
def no_of_wheels(self): # provide definition for abstract method
print("Tempo have 3 wheels")
bike = Bike()
bike.no_of_wheels()
tempo = Tempo()
tempo.no_of_wheels()
truck = Truck()
truck.no_of_wheels()
Output:
Bike have 2 wheels
Tempo have 3 wheels
Truck have 4 wheels
Here, we have an abstract class Vehicle. It is abstract because it is inheriting the abstract
class abc. The class Vehicle have an abstract method called no_of_wheels, which do not
have any definition, because abstract methods are not defined(or abstract methods remain
empty, and they expects the classes inheriting the abstract classes to provide the
implementation for the method ).
But, other classes which inherits the Vehicle class, like Bike, Tempo or Truck, defines
the method no_of_wheels, and they provide their own implementation for the abstract
method. Suppose, bike have 2 wheels, so it prints "Bike have 2 wheels" in the inherited
abstract method no_of_wheels. And, similarly, Tempo and Truck classes also provide
their own implementations.
Some notable points on Abstract classes are:
Abstract classes cannot be instantiated. In simple words, we cannot create objects for the
abstract classes.
An Abstract class can contain the both types of methods -- normal and abstract method.
In the abstract methods, we do not provide any definition or code. But in the normal
methods, we provide the implementation of the code needed for the method.
Advantages of OOPS in Python
There are numerous advantages of OOPS concepts in Python, making it favorable for
writing serious softwares. Let us look into a few of them --
Effective problem solving because, for each mini-problem, we write a class that does
what is required. And then we can reuse those classes, which makes it even quicker to
solve the next problem.
Flexibility of having multiple forms of a single class, through polymorphism
Reduced high complexity of code, through abstraction.
High security and data privacy through encapsulation.
Reuse of code, by the child class inheriting properties of parent class through inheritance.
Modularity of code allows us to do easy debugging, instead of looking into hundreds of
lines of code to find a single issue.