Introduction to Python Programming. Section 14. Object-Oriented Programming I – Introduction
- Understand the philosophy of object-oriented programming (OOP).
- Understand how OOP differs from procedural (imperative) programming.
- Understand the advantages and disadvantages of OOP.
- Learn about classes, objects, attributes, and methods.
- Learn the syntax of defining and instantiating classes in Python.
More advanced concepts such as inheritance, class hierarchy, polymorphism, and multiple inheritance will be discussed in the next Section 15.
Throughout this course, we have been putting emphasis on designing elegant and efficient algorithms and writing clean, transparent code. This included:
- Isolating reusable functionality from the rest of the code by creating custom functions.
- Isolating data from the rest of the code by using local variables.
Object-oriented programming brings these good programming practices to perfection by isolating functions and the data they work with from the rest of the code using a single entity - a class. This approach, called encapsulation, is the main idea of OOP. Encapsulation makes the code extremely well structured and as a result, even large software projects become easy to manage.
The programming style we have been using so far is called procedural (imperative) programming. The name comes from using procedures to solve various tasks that lead to the solution of the problem at hand. Procedure is an activity, a sequence of operations done with data that is supplied to it. Importantly, procedures do not own the data they operate with.
This, however, means that the data must be stored somewhere else. In other words, the range of validity of the data extends beyong the borders of the function where it is processed. Can you see an analogy to using local and global variables? It is great to keep variables local, and in the same way it would be great to make data local to procedures that operate with them. But wait, this is called object-oriented programming!
When you grasp the concept of OOP, it is easy to "fall in love" with it. There are programmers who worship OOP and make it their preferred tool for solving everything. We would like to caution you against such an attitude. Every tool was designed with some purpose in mind, and there is no tool which would be the best solution to everything. OOP brings great advantages, but it also has disadvantages. Let’s begin with the former:
- Easier troubleshooting: When a procedural code breaks down, it can take lots of time to find where it happened. In an object-oriented code, it is clear which object broke down, so the bug is easier to find.
- Easier code reuse: An object-oriented code can easily be reused through class inheritance. Encapsulated classes are also easier to port from one code to another.
- Flexibility through polymorphism: This is a truly sweet and unique advantage of OOP - the same code can do different things based on the context. For example, calling X.make_sound() will have a different outcome when X=bee and when X=lion.
- Efficient problem-solving: You already know that the best way to solve complex problems is to break them down into simple ones. Creating separate classes to handle the simple problems brings this approach to perfection.
As with everything - since there are people who love OOP, there also are people who hate it. It is good to listen to both camps because usually both have something of value to say. Here are the typical complaints against OOP:
- Object-oriented programs are longer than procedural ones.
- Object-oriented programs require more work to plan and write.
- Object-oriented programs are slower and require more resources.
Here is a famous comment about OOP (which is not entirely untrue):
"You want a banana but you get a gorilla holding the banana, and the entire jungle with it."
Let’s present an example which illustrates the difference between procedural and
object-oriented thinking. Imagine that you are moving and all your things need to be packed,
boxed, loaded on a truck, hauled 1000 miles, unloaded, and brought into your new home.
Procedural thinking: "Do it yourself!"
You (the procedure) go rent a truck, buy cardboard boxes, etc. The truck, the boxes and other
moving supplies represent data that the procedure does not own. Then you pack everything
yourself, load it on the truck, drive to the other city, unload your things, carry them into your
new home, and take out of the boxes. Then you go return the truck and throw away the
Object-oriented thinking: "Delegate the work!"
An object-oriented approach to solving the same task is very different. Instead of doing the work yourself, you call a moving company = create an instance mc of a class MovingCompany. The object mc comes with a truck, boxes, wraps, dolly, and other moving supplies automatically. They even have their own crew of big guys who can lift heavy things. In other words, you as the main program do not have to deal with the technical details of moving. This makes your life much easier, and you are free to do other things and solve other tasks. The movers will arrive at your home, and you will just call their methods mc.pack(), mc.box(), mc.load(), mc.haul(), mc.unload(), mc.carry() and mc.unpack().
Let us stay with the moving example for one more moment. There are many moving companies and all of them do basically the same thing. So, we can talk about a moving company on a general level, without having a concrete one in mind. When we do that, we talk about a concept. In object-oriented terminology, such a concept is a class.
A moving company has both the hardware (truck, boxes, dolly, straps, furniture covers, ...) and skills to use it (packing, boxing, organizing, loading, unloading, driving the truck, ...). In OOP terminology, a class owns both the data and the functionality that uses the data. The hardware is called attributes of the class, and the skills are the methods of the class.
But a class as an abstract concept will not get your things moved. For that you need a concrete moving company with a name and a phone number. Such as the company mc from the previous subsection. Such a concrete representation of a class is then called an object or an instance of the class.
Technically, methods belong to the class because that’s where they are defined, and not to the instance (object). The object just uses them. But in OOP we often say that “an object uses its methods to ...". The methods of a class can operate on data owned by the class (the moving company’s own equipment) as well as on data that does not belong to the class (your things which are being moved).
The situation is similar with attributes (data). The data can be represented as variables, lists, tuples, dictionaries, etc. They are introduced when the class is defined, but at that time they do not store any concrete values yet. Only after the class is instantiated, the variables in the object are initialized with concrete values.
Let’s see how all this is implemented in Python. We will say goodbye to the movers and look
at some geometry instead. Our first class will be named Circle. The purpose of this class is
to allow us to easily create many different circles, calculate their areas and perimeters, and
plot them with Matplotlib. Hence the class Circle will have the following attributes and
- Radius R,
- center point coordinates Cx, Cy,
- two arrays ptsx, ptsy with the X and Y coordinates of the points on the perimeter of the circle, for Matplotlib (see Subsection 3.7).
- __init__(self, r, cx, cy) ... initializer,
- area(self) ... calculate and return the area of the circle,
- perimeter(self) ... calculate and return the perimeter,
- draw(self) ... draw itself using Matplotlib.
The method __init__ is an initializer which is not exactly the same as a constructor in other object-oriented languages. But many Python programmers call it a constructor for simplicity, and we might sometimes do it as well. The initializer has two roles:
- Add new attributes to the class when the class is defined.
- Initialize them with concrete values when the class is instantiated.
The first parameter self must be included in each method of the class. This is a reference to the concrete instance through which all data and methods of the instance are accessed.
For completeness we should add that it is possible to use a different name than self, but do not do this unless your goal is to confuse everybody who will ever read your code.
Class Circle can be defined by typing
but sometimes you may see
which is also correct. This heading is followed by the definition of the initializer
and other methods. Let’s show the complete code now and then we will explain
Circle with given radius R and center point (Cx, Cy).
Default plotting subdivision: 100 linear edges.
def __init__(self, r, cx, cy, n = 100):
The initializer adds and initializes the radius R,
and the center point coordinates Cx, Cy.
It also creates the arrays of X and Y coordinates.
self.R = r
self.Cx = cx
self.Cy = cy
# Now define the arrays of X and Y coordinates:
self.ptsx = 
self.ptsy = 
da = 2*np.pi/self.n
for i in range(n):
self.ptsx.append(self.Cx + self.R * np.cos(i * da))
self.ptsy.append(self.Cy + self.R * np.sin(i * da))
# Close the polyline by adding the 1st point again:
self.ptsx.append(self.Cx + self.R)
self.ptsy.append(self.Cy + 0)
Calculates and returns the area.
return np.pi * self.R**2
Calculates and returns the perimeter.
return 2 * np.pi * self.R
def draw(self, label):
Plots the circle using Matplotlib.
plt.plot(self.ptsx, self.ptsy, label = label)
Notice that the methods are indented. You already know that the initializer __init__ both
adds attributes to the class and initializes them with values when the class is instantiated. In
particular, the lines
add to the class Circle the attributes R (radius), and Cx, Cy (center point coordinates). In the next subsection you will see how they are initialized with concrete values r, cx, cy when the class is instantiated. The arrays ptsx and ptsy are first created empty and then filled with the X and Y coordinates of the perimeter points.
Notice that all methods must have a mandatory first parameter self. This
parameter is used by Python at runtime to pass an instance of the class into the
Correspondingly, all attributes and methods must be used with the prefix self in the class methods.
Finally, notice that inside the for loop, the code uses the parametric equation of the circle which was introduced in Subsection 3.19. The code also uses standard math formulas for the area πR2 and perimeter 2πR.
With such a nice class in hand, it is easy to create many different circles, calculate their areas
and perimeters, and plot them:
C1 = Circle(1, 0, 0)
# Display the area and perimeter:
print(~Area and perimeter of circle C1:~, \
# Create an instance of class Circle named C2:
C2 = Circle(0.5, 1, 0)
# Display the area and perimeter:
print(~Area and perimeter of circle C2:~, \
# Create an instance of class Circle named C3:
C3 = Circle(0.25, 1.5, 0)
# Display the area and perimeter:
print(~Area and perimeter of circle C3:~, \
# Finally, display all circles using Matplotlib:
In the above code, line 2 creates an instance of the class Circle named C1:
The arguments 1, 0, 0 are passed directly to the initializer of the class as the radius R and
the center point coordinates Cx and Cy. The subdivision n is skipped so it will have the
default value 100. Importantly, notice that the initializer is defined with the first parameter
which is omitted when the initializer is called. This is the case not only for the initializer but
also for all other methods. The method area is defined with a single parameter
but it is called without any arguments:
The same holds for the method perimeter. The method draw is defined with two
parameters self and label,
but it is only called with one argument - the label:
Line 5 shows how the methods of the class are called – just like usual functions, except the name of the method follows the name of the instance and a period ’.’. The values of the attributes can be accessed in the same way, but it is not needed in this example.
Finally, here is the text output:
Area and perimeter of circle 2: 0.785398163397 3.14159265359
Area and perimeter of circle 3: 0.196349540849 1.57079632679
The Matplotlib plot of the three circles is shown in Fig. 66.
Table of Contents
- 1. Introduction
- 2. Using Python as a Scientific Calculator
- 3. Drawing, Plotting, and Data Visualization with Matplotlib
- 4. Working with Text Strings
- 5. Variables and Types
- 6. Boolean Values, Functions, Expressions, and Variables
- 7. Lists, Tuples, Dictionaries, and Sets
- 8. Functions
- 9. The ’For’ Loop
- 10. Conditions
- 11. The ’While’ Loop
- 12. Exceptions
- 13. File Operations
- 14. Object-Oriented Programming I – Introduction
- 15. Object-Oriented Programming II – Class Inheritance
- 16. Object-Oriented Programming III – Advanced Aspects
- 17. Recursion
- 18. Decorators
- 19. Selected Advanced Topics