Skip to content

Classes and Objects

Programming Paradigms

Before diving right into the concepts of OOP. Let's understand Programming Paradigms in Python.

Dictionary

Paradigm: a method or approach to solving a task or to developing an application.

Python supports three types of paradigms,

  1. Procedural Programming
  2. Object-Oriented Programming
  3. Functional Programming

Procedural Programming

This is the most basic form of programming. Code is structured hierarchically into smaller chunks such as loops, variables, statements and, functions and follows a top-down approach.

Code Snippet to get sum of numbers of the list,

1
2
3
4
5
6
7
8
9
# sum of elements of a list
def sum_values(my_list):
  sum_result = 0
  for item in my_list:
      sum_result += item
  return sum_result

the_list = [1,2,3,4]
print(sum_values(the_list))
Result
1
10

It is difficult to code and maintain a complex application. A less efficient and less productive way to code.

Object Orientied Programming

In this type, we are structuring an application code into objects. An object is a collection of properties(variables) and their behaviors(methods).

For example, think of email as an object.

  • Properties could be
    • recipient list
    • subject
    • body
  • Behaviour could be
    • send email
    • attach file/document
    • discard mail

Code Snippet to get sum of numbers of the list,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# sum of elements of a list
class ListOperations(object):
  def __init__(self, my_list):
    self.my_list = my_list

  def sum_values(self):
    self.sum_result = sum(self.my_list)

the_list = [1,2,3,4]
list_ops_obj = ListOperations(the_list)
list_ops_obj.sum_values()

print(list_ops_obj.sum_result)
Result
1
10

As an application becomes more complex, it's easier to work and test an independent chunk of the code and maintain the interaction of objects.

Functional Programming

As the name suggests, this paradigm uses functions as a primary method for the computations.

Here, the function describes the final result that we want rather than defining steps to get the result. The function solely depends on its input and the output values without any other mediator variables.

Code Snippet to get sum of numbers of the list,

1
2
3
4
5
6
7
# sum of elements of a list
import functools
my_list = [1, 2, 3, 4]

# functools built-in function will be covered later in the series
sum_result = functools.reduce(lambda x, y: x + y, my_list)
print(sum_result)
Result
1
10

In this part of the series, we will be covering Object Oriented Programming.

Object Oriented Programming

This paradigm relies on the concept of objects and classes.

Dictionary

Object: is a collection of data and its behavior.

Dictionary

Class: is a blue print for creating objects.

Let's understand objects and classes using a real-world example.

Let's build an application similar to Upwork. A basic template which has 3 worlds,

  • Freelancer world
    • people who apply to the jobs posted by the clients
  • Projects
    • which has a list of projects
  • Clients
    • who propose their projects and hire freelancers.

As per our example, we will call these 3 worlds class. First a freelancer class, second project class and third as client class.

Next thing is to build the blueprint of each class object.

In the real world, there is more than 1 freelancer(our instance). Each freelancer will be an independent object of our Freelancer class which will have same blueprint as we have defined but their properties data will be different.

Dictionary

Properties: are variables that contain data regarding the object of a class.

A freelancer object will have username, skills, total_earnings as its properties.

Dictionary

Methods: are like functions that can access the properties or other methods of a class to either modify the properties or to perform certain task using the properties or the arguments of the methods.

Methods are similar to functions. In the above example, we have give_testimonials, send_payment as methods of Client Object.

Declaring Class

A class is defined as,

Here class is a keyword. All the properties and methods of the class are defined inside of the class scope.

A class name must start with a letter or, an underscore and can contain only numbers, letters or underscores.

Creating Class Object

We define a class using,

1
2
3
4
5
class MyClass:
    pass

obj = MyClass() # creating MyClass Object
print(obj)
Result
1
<__main__.MyClass object at 0x7f7baadc6c70>

Printing obj shows the memory location of the object. The above class does not have any properties or methods.

Implementing Properties

Let's implement our Freelancer Class properties.

1
2
3
4
5
6
7
class Freelancer:
    # define and initialize properties
    username = None
    completed_projects = 0
    skills = []
    total_earnings = 0.0
    hourly_rate = 0.0

We have defined and initialized the properties of a freelancer object. Remember it's a blueprint, so what works for 1 freelancer should work for another freelancer as well.

Info

The way we have defined our properties are know as class properties. In this type of declaration all the object will share same data of the properties.

In Intializing Objects section, we will see how we can have different data for different objects of the class.

Access and Assign

To access a class property we first define our object similar to the previous section. Then using .(dot) notation we access the properties.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Freelancer:
    # define and initialize properties
    username = None
    completed_projects = 0
    skills = []
    total_earnings = 0.0
    hourly_rate = 0.0

# creating an object for freelancer 1 Anu
anu_fl = Freelancer()

# assign values to anu_fl object
anu_fl.username = 'Anu'
anu_fl.completed_projects = 2
anu_fl.skills = ['python','data science']
anu_fl.total_earnings = 220
anu_fl.hourly_rate = 25

# print data of freelancer 1
print(f"Username : {anu_fl.username}")
print(f"Completed Projects : {anu_fl.completed_projects}")
print(f"Skills : {anu_fl.skills}")
print(f"Total Earnings : {anu_fl.total_earnings}")
print(f"Hourly Rate : {anu_fl.hourly_rate}")
Result
1
2
3
4
5
Username : Anu
Completed Projects : 2
Skills : ['python', 'data science']
Total Earnings : 220
Hourly Rate : 25

Initializing Objects

We define an initializer function __init__ of a class to initialize the object and steps are performed as we create the object of the class. It is used to define and assign data to instance variables.

This special type of function does not have return type. Any function or method of a class has its first parameter as self which refers to the object. More on this later. Let's dive into code first,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Freelancer:
    def __init__(self, username, completed_projects, skills, earnings, rate):
        # define and initialize properties
        self.username = username
        self.completed_projects = completed_projects
        self.skills = skills
        self.total_earnings = earnings
        self.hourly_rate = rate

# creating an object for freelancer 1 Anu
anu_fl = Freelancer('Anu', 2, ['python','data science'], 220, 25)

# print data of freelancer 1
print(f"Username : {anu_fl.username}")
print(f"Completed Projects : {anu_fl.completed_projects}")
print(f"Skills : {anu_fl.skills}")
print(f"Total Earnings : {anu_fl.total_earnings}")
print(f"Hourly Rate : {anu_fl.hourly_rate}")
Result
1
2
3
4
5
Username : Anu
Completed Projects : 2
Skills : ['python', 'data science']
Total Earnings : 220
Hourly Rate : 25

Class variable vs Instance variable

A class property or a method is shared with all the objects of the class whereas an instance property or a method is unique to each object of the class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Project:
    projectId = "001"   # class variable

    def __init__(self, project_title):
        self.project_title = project_title  # instance variable


p1 = Project("Python CV")   # object instance 1
p2 = Project("Mern Project")    # object instance 2

print(f"P1 obj project Id : {p1.projectId}")
print(f"P1 obj project title : {p1.project_title}")
print(f"P2 obj project Id : {p2.projectId}")
print(f"P2 obj project title : {p2.project_title}")
Result
1
2
3
4
P1 obj project Id : 001
P1 obj project title : Python CV
P2 obj project Id : 001
P2 obj project title : Mern Project

As you can see, if we define projectId as a class property, all the objects that we create will have the same projectId. This is a wrong use of class variable and not recommended 🙅🏻.

Implementing Methods

In this section, we will learn how to make use of properties. Methods can be used to alter the properties values or can be used to perform some computations.

Tip

Methods are similar to functions that we learned in Module 1.

There are mainly 3 types of methods that you can create,

  1. instance methods
  2. class methods
  3. static methods

Dictionary

Method: a group of statements to perform a specific task.

One difference between a normal function and a class method is the use of the self argument.

self Argument

Let's understand using an example.

First, make use of self. This is a standard way of defining class and its methods. This way of defining method is called instance method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class CheckEqual:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def isEqual(self):
        return self.x == self.y

p = CheckEqual(1, 2) # create an instance object
print(p.x)
print(p.isEqual())
Result
1
2
1
False

Now, let's remove self from the equation and run the code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class CheckEqual:
    def __init__(x, y):
        x = x
        y = y

    def isEqual():
        return x == y

p = CheckEqual(1, 2) # create an instance object
print(p.x)
print(p.isEqual())
Result
1
2
3
4
Traceback (most recent call last):
File "temp.py", line 9, in <module>
    p = CheckEqual(1, 2) # create an instance
TypeError: __init__() takes 2 positional arguments but 3 were given

The error says init method takes 2 positional arguments but 3 were given.

We only passed 2 arguments into our CheckEqual class; it got 3 arguments because self was passed in as the first argument. Therefore, the first argument to every method is going to be self.

Info

The self argument is only passed in the method definition and not used when the method is called.

Let's understand what self stands for,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class CheckEqual:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def isEqual(self):
        return self.x == self.y

    def getLocation(self):
        return id(self)

p = CheckEqual(1, 2) # create an instance
print(id(p))    # location of our instance
print(p.getLocation())  # location of self from method
Result
1
2
140541771414688
140541771414688

Info

The built-in id function returns a number representing the memory location of the object.

So self is just a variable that points to the instance of our class that we're currently working with.

But if it is just a variable what if we don't use self but any other name?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class CheckEqual:
    def __init__(point, x, y):
        point.x = x
        point.y = y

    def isEqual(point):
        return point.x == point.y

p = CheckEqual(1, 2) # create an instance
print(p.x)
print(p.isEqual())
Result
1
2
1
False

It still works!!!

Tip

The word self is a convention used by Python developers. It's better to use self to improve the readability of the code.

Class Methods

Class methods work with class properties mentioned earlier. We can declare a class method to modify class properties.

Info

Class methods can be accessed directly using class name using notation,

class_name.class_method()

We use a decorator @classmethod coded as below. Similar to self we use cls as the convention for the first argument for the class method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class CricketTeam:
    team_name = "India"

    @classmethod
    def get_team_name(cls):
        return cls.team_name

    @classmethod
    def change_team_name(cls, name):
        cls.team_name = name


print(CricketTeam.get_team_name())  # calling method using class name
print("Change team name operation.")
CricketTeam.change_team_name("New Zealand")
print(f"Changed team name : {CricketTeam.get_team_name()}")
Result
1
2
3
India
Change team name operation.
Changed team name : New Zealand

Tip

Comment out the @classmethod line and then run to see the difference.

When you remove @classmethod, the function becomes instance method and instance method cannot be directly called using the class name.

Static Methods

Static methods are used as a utility function that takes argument if provided and works on those parameters. It does not use self or cls as the first argument.

We use a decorator @staticmethod coded as below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class CricketTeam:
    team_name = "India"

    @staticmethod
    def demo():
        return "I am a static method"


team = CricketTeam() # create an instance

print(team.demo()) # calling method using class instance
print(CricketTeam.demo())  # calling method using class name
Result
1
2
I am a static method
I am a static method

Tip

A static method cannot modify class or instance properties.

Let's define everything in one code and see the difference.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class CricketTeam:
    teamName = "Australia"  # class property

    def __init__(self, captain_name):
        self.captain_name = captain_name

    def change_captain_name(self, name):
        self.captain_name = name

    def change_team_name(self, name):
        self.teamName = name

    @classmethod
    def change_captain_name_cls(cls, name):
        cls.captain_name = name

    @classmethod
    def change_team_name_cls(cls, name):
        cls.teamName = name

    @staticmethod
    def change_captain_name_stat(name):
        captain_name = name

    @staticmethod
    def change_team_name_stat(name):
        teamName = name    

team1 = CricketTeam("Dhoni")
team2 = CricketTeam("Kane")

print("Team 1 info")
print(team1.captain_name)
print(team1.teamName)

print("\nTeam 2 info")
print(team2.captain_name)
print(team2.teamName)

print("\nChange Captain and Team Name of Team 1")
team1.change_team_name("India")
team1.change_captain_name("Virat")

print("Change Captain and Team Name of Team 2")
team2.change_team_name("New Zealand")
team2.change_captain_name("Southe")

print("-------------")
print("Changed team name and captain name")
print("-------------")
print("Team 1 info")
print(team1.captain_name)
print(team1.teamName)

print("\nTeam 2 info")
print(team2.captain_name)
print(team2.teamName)

print("\nPrint using class name")
# print(CricketTeam.captain_name)   # captain_name is an instance object
print(CricketTeam.teamName)

print("\nChange Captain and Team Name using class name")
CricketTeam.change_team_name_cls("South Africa")
CricketTeam.change_captain_name_cls("Faf")

print("\nPrint using class name")
# print(CricketTeam.captain_name)
print(CricketTeam.teamName)

"""
The below code doesn't work 
because the static method cannot 
access or modify 
the instance or the class properties.
"""
print("\nChange Captain and Team Name using static method for team 1")
team1.change_team_name_stat("England")
team1.change_captain_name_stat("Morgan")

print("\nChange Captain and Team Name using static method for team 2")
team2.change_team_name_stat("Australia")
team2.change_captain_name_stat("Finch")


print("-------------")
print("Changed team name and captain name using static method")
print("-------------")
print("Team 1 info")
print(team1.captain_name)
print(team1.teamName)

print("\nTeam 2 info")
print(team2.captain_name)
print(team2.teamName)
Result
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Team 1 info
Dhoni
Australia

Team 2 info
Kane
Australia

Change Captain and Team Name of Team 1
Change Captain and Team Name of Team 2
-------------
Changed team name and captain name
-------------
Team 1 info
Virat
India

Team 2 info
Southe
New Zealand

Print using class name
Australia

Change Captain and Team Name using class name

Print using class name
South Africa

Change Captain and Team Name using static method for team 1

Change Captain and Team Name using static method for team 2
-------------
Changed team name and captain name using static method
-------------
Team 1 info
Virat
India

Team 2 info
Southe
New Zealand

Takeaways

  • In the Instance method, using the self parameter, we can freely access and modify properties and methods on the same object.
  • In the Class method, using the cls parameter, we can access and modify class attributes and other class methods. As we don't use the self parameter, the class method cannot modify an instance object.
  • In the Static method, there is no use of either self or cls as a parameter. Therefore it cannot modify any class objects or instance objects.

In the next section, we will start with Information Hiding.

Back to top