Understanding Object Oriented Programming in Python

Sarath Pillai's picture
OOP & Python

Before jumping into Python language specifics for object oriented programing, let’s first imagine couple of software projects and then start thinking about different solutions to address those projects. This way we will be able to understand different aspects of OOP(Object Oriented Programming), before we start using it.

Imagine we are given a task to develop a software for a school. The software should be able to enroll students, assign them courses, and evaluate them etc.

There will be numerous number of students who will get enrolled each year. So there needs to be a method where we can easily create students and do operations on them(like enroll them to a course, assign them a division, a classroom, a mentor etc.). Each student should also provide details like First name, Last name, Age, Course name etc.

So we need a prototype(a skeleton that will define the characteristics and features) of a student for our program. Or we need a blueprint for a student. This way, new students can be created based upon the blueprint.

Creation of students and operations on them are all repetitive (because we have many students coming in each year for different courses). So we need a blueprint, to create students over and over again, with identical set of characteristics and features. This sort of blueprint will also enforce some sort of uniformity across all students. For example, you can cannot then create a student without specifying the age of the student, because the blue print will always enforce several input parameters without which a student cannot be created/enrolled.

Let’s imagine another example.. A banking software. Each customer should have a name, address, locality, account number, identity number etc.

Here also we need a blueprint for a customer with identical set of characteristics, so that future customers can be created very easily.

 

Although we are discussing a real world example, one thing to note here is… A computer deals primarily with data(be that a student or a customer, its nothing but data). So “A Student” OR “A Bank Customer” is nothing but a collection of data, like “name”, “address”, “age”, “course”, “account no”, and their behaviors(will discuss about behaviors later).

 

In Object Oriented Programming concept, every student we enroll or each bank customer we add is called an Object. Every object will have similar characteristics and similar set of behaviors, but they are all different and unique objects. You might have 100 students enrolled in a course, and each of these students will be distinct. But all these students will have the same set of properties and behaviors. This is because all of them were/will be created from the same blueprint.

 

The blueprint used to create students and customers for our bank is known as a Class. So basically classes define objects and their characteristics.

 

Let's say we have two new students joining our school this year. First student is named "John" and second student is named "Mary". Both these students will have different(distinct) set of first name, last name, age, and course. Both these students were created in our software from the same blueprint(class) called "student". In OOP(Object Oriented Programming), object John is called an instance of the class student. Object Mary is also called another instance of class student.

 

So basically objects are nothing but instances of class, with its own set of data and characteristics. The data associated with each student object(instance of class), is also sometimes called as attributes. For example, all students will have first name attribute, but each student might have different values for the first name attribute. Similarly each student will have age attribute, but might have different values for age attribute.

 

Its also very important to realize that attributes does not need to be unique. ie: There is no enforcement of uniqueness of age or first name associated with each instances/objects of class student. Also keep the fact in mind that attributes are often referred to as "properties" in several places.

 

I think we now have some basics at hand. Whatever we discussed till now, only dealt with the data associated with an object. Its good that we can define a blueprint(class) and create instances(objects) from that blueprint(class), but its not that useful, if we are not able to do different operations on that object. Or make that object do operations on other objects. If this sounds a bit confusing, let's think a bit more about the school/student enrollment program.

 

Although we only talked about one single blueprint(class), for creating student objects, we still have many other repetitive operations that can happen on an application like this.

 

  • Think of courses a student can enroll to. Each course provided by the school should have a "Course Name", "Course Duration", "Course Curriculum" and so on. So may be its time for us to think about having another blueprint(class) for course?
  • Teachers/Mentors/Trainers. Each course will have an associated trainer/mentor. Similar to students. They should have their "Qualification", "Age", and "Previous Experience" etc recorded. So yet again anther blueprint(class)?

 

Also we should not forget the fact that a student object(instance of student class/blueprint) should be able to do the job of enrollment to another object called course(created from another blueprint/class called course). So enrollment is an operation done on a student object. Similarly, a mentor/trainer also needs to associated with a course object. We should be able to remove a student object from a course and add that student to another course. We should be able to remove a particular trainer/mentor and make him part of another course.

 

These are all operations that needs to be carried out on objects, by other objects(basically operations should happen between objects)

 

As we are discussing object oriented programming in Python, let's now get straight to python objects. Let's see some python related syntax to implement whatever we discussed till now.

 

Creating Classes in Python:

 

This is the beauty of Python language. It's probably the most simplest language on earth, to read and understand(its syntax is very simple and straight forward).  Let's create a text file with whatever name you like with the extension of .py (for example, student.py)

 

class Student:
   pass

 

That's our first class(our blueprint). the "class" keyword let's you create a class, which is followed by the name of the class and a colon to indicate the beginning of the code that is part of that class. As of now the class does not do anything. It's just a blueprint with no content(no data, no characteristics and behavior)

pass keyword actually does not do anything. Its a keyword used to indicate nothing/no action, but something is required there in that position syntactically.

 

Let's add a bit more code to the above file.  Although we have only created a blueprint that does nothing, let's try creating instances(basically some objects. Or in our case, let's create two students).

Let's modify the file we created, with the below.

 

class Student:
   pass

john = Student()
mary = Student()
print john
print mary

we have now defined two students from the blueprint we created called "Student". So basically "john" and "mary" are distinct objects/instances created from the class "Student".

Let's now try executing that python file and see what's the output.

 

#python student.py
<__main__.Student instance at 0x103b48bd8>
<__main__.Student instance at 0x103b48c20>

 

The most important thing to understand here about the output is...That both "john" and "mary" are two instances of Student class at different memory locations. As they are using different memory locations, both are distinct. So basically printing out the two student instances we created just tells us their memory location and blueprint from which it was created.

 

As everything we did till now in our blueprint(Student class) did not involve any sort of data or characteristics that we discussed earlier, let's add some sort of data in there.

 

Let's modify our student.py file as shown below.

 

class Student:
   pass

john = Student()
mary = Student()
john.first_name = "John"
john.last_name = "Smith"
john.age = 19
mary.first_name = "Mary"
mary.last_name = "Williams"
mary.age = 20

print "John\'s last name: %s" % john.last_name
print "Mary's Age: %d" %  mary.age

 

The dot operator we used to assign parameter's uses the syntax of <object>.<parameter-name> = <value>. This value can be anything. A string, number, another object, another function etc.

We have actually not touched the class at all. We just added data to john and mary objects by using dot(.) operator. We have actually defined new parameters for these objects outside the class definition, without touching the class. We can go on adding whatever parameters we like to our objects using the dot operator and then the name of the parameter.

We also can enforce a certain set of parameters to new objects, while they are created from a class as shown below.

 

This sort of enforcement of parameters, while creating a class can be done by adding an initialization section to our class definition.

 

class Student:
   def __init__(self, first_name, last_name, age):
       self.first_name = first_name
       self.last_name = last_name
       self.age = age
   pass

 

Now let's create our objects/two students(John, Mary) from the newly modified Student class.

 

class Student:
   def __init__(self, first_name, last_name, age):
       self.first_name = first_name
       self.last_name = last_name
       self.age = age
   pass

john = Student("John", "Smith", 19)
mary = Student("Mary", "Williams", 20)

print "John\'s last name: %s" % john.last_name
print "Mary's Age: %d" %  mary.age

 

 

Our code has now become a lot more shorter. We have now supplied three parameters that are pre-requisite while creating any student instance(object). The output of the program will still be the same. But we have now enforced first name, last name, and age while defining a student object.

If you create a student object without any of these parameters, it will throw the below error.

 

Traceback (most recent call last):
  File "test1.py", line 8, in <module>
    john = Student("John", "Smith")
TypeError: __init__() takes exactly 4 arguments (3 given)

 

We actually created a method inside our Student class, that enforced 3 parameters. Basically its very similar to how a function is defined in Python. Functions defined inside a class which adds characteristics and behaviors in the class are known as methods. Now let's try to understand what's the __init__ keyword and self keyword used in the modified Student class.

 

Have you noticed the error shown above?. The error says "TypeError: __init__() takes exactly 4 arguments (3 given)". What does that mean? We have  told Student class to expect for three arguments while creating the object(ie: first_name, last_name, and age). But why is the error telling that it takes 4 arguments?.

This is because, __init__ is nothing but a constructor that creates and initiates our object/instance. This is because the object you create itself is the first parameter to __init__. For example, when we create "john" object, that object itself is the very first argument to __init__ instructor. That is why you see an additional parameter called self in there.

Self represents the object you are creating from that class. If you want to use __init__, without having to enforce any parameters, then also you need to provide one parameter. That is the self parameter. See the below code block.

 

class Student:
   def __init__(self):
       pass

john = Student()
mary = Student()
john.first_name = "John"
john.last_name = "Smith"
john.age = 19
mary.first_name = "Mary"
mary.last_name = "Williams"
mary.age = 20

print "John\'s last name: %s" % john.last_name
print "Mary's Age: %d" %  mary.age

 

Even though we did not enforce any parameter, we still had to give self keyword inside __init__. This is because self is considered as the object that gets created. So if you are using __init__ you will have to use self. Well the keyword self is just being used out of convention. You can actually use anything you like there. Instead of self, let's try xyz there. 

 

class Student:
   def __init__(xyz):
       pass

 

 

The above should also work. But the bottom line is that "you have to give some argument inside __init__". Without an argument, python will throw an error. This is because instructor function creates and initializes the object and the function needs something in there to represent the object. Hence we need a parameter there. It can be anything(self, xyz, abx or whatever you like.)

 

Let's now add several basic behaviors to our class. So that any object created from that class can have those functionalities. See below.

class Student:
   def __init__(self, first_name, last_name, age):
       self.first_name = first_name
       self.last_name = last_name
       self.age = age
   def enroll_to_course(self, course_name="general"):
       self.course_name = course_name
   def remove_from_course(self):
       self.course_name = "None"

 

We just added 2 basics functions inside the class for showing an example. These are called methods. Any object/instance created from this class will have these methods to execute.

For example, we can create two objects(Mary and John), and then enroll them and remove them from courses. We also have configured a default course while enrolling(if no specific course is mentioned by the user).

 

Let's now create objects and invoke those methods we created.

 

class Student:
   def __init__(self, first_name, last_name, age):
       self.first_name = first_name
       self.last_name = last_name
       self.age = age
   def enroll_to_course(self, course_name="general"):
       self.course_name = course_name
   def remove_from_course(self):
       self.course_name = "None"

john = Student("John", "Smith", 19)
mary = Student("Mary", "Williams", 20)
john.enroll_to_course("Science")
mary.enroll_to_course("Commerce")

print "John\'s last name: %s" % john.last_name
print "Mary's Age: %d" %  mary.age
print "John is enrolled to the course: %s" % john.course_name
john.remove_from_course()
mary.enroll_to_course()
print "John is enrolled to the course: %s" % john.course_name
print "Mary is enrolled to the course: %s" % mary.course_name

 

The method enroll_to_course has one parameter(called course_name.). With a default value of "general". This value gets applied to the object while invoking this method without any other course name as parameter. So basically if you invoke the method as mary.enroll_to_course(), Mary will have the course of "general". If you invoke the method as mary.enroll_to_course("Commerce"), Mary will then have a course named commerce applied to her.

Accessing a method available for an object is also done by using the dot operator, with the exact same syntax(with required parameters).

 

We have another method called remove_from_course, which will remove a student object from a particular course, and assign a value of "None".

If you want an attribute with the same value for all instances/objects of a class, then you can directly define it inside the class, in the same way you define any other variable in python(but has to be inside the class definition). In our example, let's imagine we have a mentor, who will act as a common mentor for all students(common for all student objects).

 

class Student:
   mentor = "Sam"
   def __init__(self, first_name, last_name, age):
       self.first_name = first_name
       self.last_name = last_name
       self.age = age
   def enroll_to_course(self, course_name="general"):
       self.course_name = course_name
   def remove_from_course(self):
       self.course_name = "None"

john = Student("John", "Smith", 19)
mary = Student("Mary", "Williams", 20)

print "John's last name: %s" % john.last_name
print "Mary's Age: %d" %  mary.age
print "Mary's mentor is : %s" % mary.mentor
print "John's mentor is : %s" % john.mentor

 

Imagine we now have to hire few professors/mentors/other employees etc. As we are talking about object oriented programming, we will have to start thinking about other classes as well. We now need another class called employees or something like that.

Employees also will have first name, last name, age, qualifications etc. So we now have two classes in total(which are almost identical to a certain extent). See below.

 

class Student:
   def __init__(self, first_name, last_name, age):
       self.first_name = first_name
       self.last_name = last_name
       self.age = age
class Employee:
   def __init__(self, first_name, last_name, age):
       self.first_name = first_name
       self.last_name = last_name
       self.age = age
john = Student("John", "Smith", 19)
sam = Employee("Sam", "Williams", 39)

print "John's last name: %s" % john.last_name
print "Sam's mentor is : %s" % sam.last_name

 

The above shown method is a really bad idea. This is because repetition or duplication of code in programming is considered really bad and can get error prone as the program gets bigger and bigger.

To solve this, when it comes to object oriented programming, Python has something called as inheritance for you. You basically can inherit features from another class while defining a new class.

This can prevent repetition/duplication of code a lot. We actually need to rethink the way we thought about students and employees a bit. A good idea is to create a parent class named Person OR People or similar(because at the end of the day, students, employees will all have a set of similar data).

But we still need student and employee classes. This is because each of these object types, will have their own individual set of behaviors(methods). Like for example, employees needs to be assigned to a department, and might have salaries and other details. Students might need to register for a course etc. So behaviors are different, but several characteristics might be of the same nature.

 

class Person:
   def __init__(self, first_name, last_name, age):
       self.first_name = first_name
       self.last_name = last_name
       self.age = age
class Student(Person):
   pass
class Employee(Person):
   pass
john = Student("John", "Smith", 19)
sam = Employee("Sam", "Williams", 39)

print "John's last name: %s" % john.last_name
print "Sam's mentor is : %s" % sam.last_name

 

So now we do not have duplicate code while we defined Student and Employee class. Inheritance is quite simple to implement in Python. Its as simple as providing the class name to inherit from as an argument to the class definition. Like for example class Abc(Xyz): (will inherit features of Xyz class while creating Abc class).

 

Python will always inherit a new class from a super class known as "Object"(if you don't specify your own class to inherit from. Python will inherit new classes from Object class). Don't confuse this with the objects we create from a class. All classes in Python are created from a super class named Object.

 

class Student(Object):
   pass
class Student:
   pass

 

 

Both the examples shown above does the same thing. No difference.

So inheritence is a nice method to add more features and behaviours to an existing class, and you can also override and edit features in a super class by directly adding new ones in the subclass.

 

Generally we start writing programs by defining variables, and functions to do different operations on our variables. When you feel we are creating more set of related variables(similar kind of variables with different names), and repeatedly doing similar set of operations on these variables, then its a good idea to define a class along with the functions as methods inside.

Rate this article: 
Average: 5 (1 vote)

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Type the characters you see in this picture. (verify using audio)
Type the characters you see in the picture above; if you can't read them, submit the form and a new image will be generated. Not case sensitive.