A Beginner’s Guide to Python Object-Oriented Programming (OOP)
Programming is an art. And as in art, selecting the proper brushes and paints is essential to produce the best works. Python Object-Oriented Programming is one such skill.
Choosing the right programming language is a crucial part of any project, and it can lead either to a fluid and enjoyable development or a complete nightmare. Hence, it would be best if you used the best-fit language for your use case.
That’s the primary reason to learn object-oriented programming in Python, which is also one of the most popular programming languages.
An Example Python Program
Before digging into the matter, let us pose a question: have you ever written a Python program like the one below?
secret_number = 20 while True: number = input('Guess the number: ') try: number = int(number) except: print('Sorry that is not a number') continue if number != secret_number: if number > secret_number: print(number, 'is greater than the secret number') elif number < secret_number: print(number, 'is less than the secret number') else: print('You guessed the number:', secret_number) break
This code is a simple number guesser. Try to copy it in a Python file and run it in your system. It perfectly accomplishes its purpose.
But here comes a huge problem: what if we asked you to implement a new feature? It could be something simple — for instance:
“If the input is a multiple of the secret number, give the user a hint.”
The program would quickly grow complex and heavy as you increase the number of features and, therefore, the total number of nested conditionals.
That’s precisely the problem object-oriented programming tries to solve.
Requirements to Learn Python OOP
Before heading into object-oriented programming, we strongly recommend you have a firm grasp of Python basics.
Classifying topics considered “basic” can be difficult. Because of this, we’ve designed a cheat sheet with all the main concepts needed to learn Object-Oriented programming in Python.
- Variable: Symbolic name that points to a specific object (we’ll see what objects mean through the article).
- Arithmetic operators: Addition (+), subtraction (-), multiplication (*), division (/), integer division (//), modulo (%).
- Built-in data types: Numeric (integers, floats, complex), Sequences (strings, lists, tuples), Boolean (True, False), Dictionaries, and Sets.
- Boolean expressions: Expressions in which the result is True or False.
- Conditional: Evaluates a boolean expression and does some process depending on the result. Handled by if/else statements.
- Loop: Repeated execution of code blocks. It can be for or while loops.
- Functions: Block of organized and reusable code. You create them with the keyword def.
- Arguments: Objects passed to a function. For instance:
sum([1, 2, 4])
- Run a Python script: Open a terminal or command line and type “python <name of the file>.”
- Open a Python Shell: Open a terminal and type
python3depending on your system.
Now you have these concepts crystal clear, you can move forward with understanding object-oriented programming.
What Is Object-Oriented Programming in Python?
Object-Oriented Programming (OOP) is a programming paradigm in which we can think about complex problems as objects.
A paradigm is a theory that supplies the base for solving problems.
So when we’re talking about OOP, we’re referring to a set of concepts and patterns we use to solve problems with objects.
An object in Python is a single collection of data (attributes) and behavior (methods). You can think of objects as real things around you. For example, consider calculators:
As you may notice, the data (attributes) are always nouns, while the behaviors (method) are always verbs.
This compartmentalization is the central concept of Object-Oriented Programming. You build objects that store data and contain specific kinds of functionality.
Why Do We Use Object-Oriented Programming in Python?
OOP allows you to create secure and reliable software. Many Python frameworks and libraries use this paradigm to build their codebase. Some examples are Django, Kivy, pandas, NumPy, and TensorFlow.
Let’s see the main advantages of using OOP in Python.
Advantages of Python OOP
The following reasons will make you opt for using object-oriented programming in Python.
All Modern Programming Languages Use OOP
This paradigm is language-independent. If you learn OOP in Python, you’ll be able to use it in the following:
All of these languages are either natively object-oriented or include options for object-oriented functionality. If you want to learn any of them after Python, it’ll be easier — you’ll find many similarities between languages working with objects.
OOP Allows You to Code Faster
Coding faster doesn’t mean writing fewer lines of code. It means you can implement more features in less time without compromising the stability of a project.
Object-Oriented programming allows you to reuse code by implementing abstraction. This principle makes your code more concise and legible.
As you may know, programmers spend much more time reading code than writing it. It’s the reason legibility is always more important than getting features out as quickly as possible.
You’ll see more about the abstraction principle later.
OOP Helps You Avoid Spaghetti Code
Do you remember the number guesser program at the start of this article?
If you keep adding features, you’ll have many nested if statements in the future. This tangle of endless lines of code is called spaghetti code, and you should avoid it as much as possible.
OOP gives us the possibility of compressing all the logic in objects, therefore avoiding long pieces of nested if’s.
OOP Improves Your Analysis of Any Situation
Once you get some experience with OOP, you’ll be able to think of problems as small and specific objects.
This understanding leads to a rapid project initialization.
Structured Programming vs Object-Oriented Programming
Structured programming is the most-used paradigm by beginners because it is the simplest way to build a small program.
It involves running a Python program sequentially. That means you’re giving the computer a list of tasks and then executing them from top to bottom.
Let’s see an example of structured programming with a coffee shop program.
small = 2 regular = 5 big = 6 user_budget = input('What is your budget? ') try: user_budget = int(user_budget) except: print('Please enter a number') exit() if user_budget > 0: if user_budget >= big: print('You can afford the big coffee') if user_budget == big: print('It's complete') else: print('Your change is', user_budget - big) elif user_budget == regular: print('You can afford the regular coffee') print('It's complete') elif user_budget >= small: print('You can buy the small coffee') if user_budget == small: print('It's complete') else: print('Your change is', user_budget - small)
The code above acts as a coffee shop vendor. It’ll ask you for a budget, then “sell” you the biggest coffee you’re capable of buying.
Try to run it in the terminal. It’ll execute step by step, depending on your input.
This code works perfectly, but we have three problems:
- It has a lot of repeated logic.
- It uses many nested if conditionals.
- It’ll be hard to read and modify.
OOP was invented as a solution to all of these problems.
Let’s see the above program implemented with OOP. Don’t worry if you don’t understand it yet. It’s only for comparing structured programming and object-oriented programming.
class Coffee: # Constructor def __init__(self, name, price): self.name = name self.price = float(price) def check_budget(self, budget): # Check if the budget is valid if not isinstance(budget, (int, float)): print('Enter float or int') exit() if budget < 0: print('Sorry you don't have money') exit() def get_change(self, budget): return budget - self.price def sell(self, budget): self.check_budget(budget) if budget >= self.price: print(f'You can buy the self.name coffee') if budget == self.price: print('It's complete') else: print(f'Here is your change self.get_change(budget)$') exit('Thanks for your transaction')
Note: All the following concepts will be explained deeper through the article.
The above code represents a class named “Coffee.” It has two attributes — “name” and “price” — and they’re both used in the methods. The primary method is “sell,” which processes all the logic needed to complete the selling process.
If you try to run that class, you won’t get any output. It primarily occurs because we’re just declaring the “template” for the coffees, not the coffees themselves.
Let’s implement that class with the following code:
small = Coffee('Small', 2) regular = Coffee('Regular', 5) big = Coffee('Big', 6) try: user_budget = float(input('What is your budget? ')) except ValueError: exit('Please enter a number') for coffee in [big, regular, small]: coffee.sell(user_budget)
Here we’re making instances, or coffee objects, of the class “Coffee,” then calling the “sell” method of each coffee until the user can afford any option.
We’ll get the same output with both approaches, but we can extend the program functionality far better with OOP.
Below is a table comparing object-oriented programming and structured programming:
|Easier to maintain||Hard to maintain|
|Don’t Repeat Yourself (DRY) approach||Repeated code in many places|
|Little pieces of code reused in many places||A large amount of code in few places|
|Object approach||Block code approach|
|Easier to debug||Harder to debug|
|Big learning curve||Simpler learning curve|
|Used in large projects||Optimized to simple programs|
To conclude the paradigm comparison:
- Neither paradigm is perfect (OOP can be overwhelming to use in simple projects).
- These are just two ways of solving a problem; there are others out there.
- OOP is used in large codebases, while structured programming is mainly for simple projects.
Let’s move on to built-in objects in Python.
Everything Is an Object in Python
We’ll tell you a secret: you’ve been using OOP all the time without noticing it.
Even when using other paradigms in Python, you still use objects to do almost everything.
That’s because, in Python, everything is an object.
Remember the definition of object: An object in Python is a single collection of data (attributes) and behavior (methods).
That matches any data type in Python.
A string is a collection of data (characters) and behaviors (upper(), lower(), etc..). The same applies to integers, floats, booleans, lists, and dictionaries.
Before continuing, let’s review the meaning of attributes and methods.
Attributes and Methods
Attributes are internal variables inside objects, while methods are functions that produce some behavior.
Let’s do a simple exercise in the Python shell. You can open it by typing
python3 in your terminal.
Now, let’s work with the Python shell to discover methods and types.
>>> kinsta="Kinsta, Premium WordPress hosting" >>> kinsta.upper() 'KINSTA, PREMIUM WORDPRESS HOSTING'
In the second line, we’re calling a string method, upper(). It returns the content of the string all in uppercase. However, it doesn’t change the original variable.
>>> kinsta 'Kinsta, Premium WordPress hosting'
Let’s delve into valuable functions when working with objects.
The type() function allows you to get the type of an object. The “type” is the class to which the object belongs.
>>> type(kinsta) # class 'str'
The dir() function returns all the attributes and methods of an object. Let’s test it out with the kinsta variable.
>>> dir(kinsta) ['__add__', '__class__', ........... 'upper', 'zfill']
Now, try to print some of the hidden attributes of this object.
>>> kinsta.__class__ # class ‘str’ e>
This will output the class the object kinsta belongs to. So we can say the only thing the type function returns is the __class__ attribute of an object.
You can experiment with all the data types, discovering all of their attributes and methods directly on the terminal. You can learn more about the built-in data types in the official documentation.
Your First Object in Python
A class is like a template. It allows you to create custom objects based on the attributes and methods you define.
You can think of it as a cookie-cutter that you modify to bake the perfect cookies (objects, not tracking cookies), with defined characteristics: Shape, Size, and more.
On the other hand, we have instances. An instance is an individual object of a class, which has a unique memory address.
Now that you know what classes and instances are, let’s define some!
To define a class in Python, you use the class keyword, followed by its name. In this case, you’ll create a class named Cookie.
Note: In Python, we use the camel case name convention to name classes.
class Cookie: pass
Open your Python shell and type the code above. To create an instance of a class, just type its name and parenthesis after it. It’s the same process as invoking a function.
cookie1 = Cookie()
Congratulations — you’ve just created your first object in Python! You can check its id and type with the following code:
id(cookie1) 140130610977040 # Unique identifier of the object type(cookie1) <class '__main__.Cookie'>
As you can see, this cookie has a unique identifier in memory, and its type is Cookie.
You can also check if an object is an instance of a class with the isinstance() function.
isinstance(cookie1, Cookie) # True isinstance(cookie1, int) # False isinstance('a string', Cookie) # False
The __init__() method is also named “constructor.” It’s called Python each time we instantiate an object.
The constructor creates the object’s initial state with the minimum set of parameters it needs to exist. Let’s modify the Cookie class, so it accepts parameters in its constructor.
class Cookie: # Constructor def __init__(self, name, shape, chips="Chocolate"): # Instance attributes self.name = name self.shape = shape self.chips = chips
In the Cookie class, every cookie must have a name, shape, and chips. We’ve defined the last one as “Chocolate.”
On the other hand, self refers to the instance of the class (the object itself).
Try to paste the class in the shell and create an instance of the cookie as usual.
cookie2 = Cookie() # TypeError
You’ll get an error. That’s because you must provide the minimal set of data the object needs to live — in this case, name and shape since we’ve already set chips to “Chocolate.”
cookie2 = Cookie('Awesome cookie', 'Star')
To access the attributes of an instance, you must use the dot notation.
cookie2.name # 'Awesome cookie' cookie2.shape # 'Star' cookie2.chips # 'Chocolate'
For now, the Cookie class doesn’t have anything too juicy. Let’s add a sample method bake() to make things more interesting.
class Cookie: # Constructor def __init__(self, name, shape, chips="Chocolate"): # Instance attributes self.name = name self.shape = shape self.chips = chips # The object is passing itself as a parameter def bake(self): print(f'This self.name, is being baked with the shape self.shape and chips of self.chips') print('Enjoy your cookie!')
To call a method, use the dot notation and invoke it as a function.
cookie3 = Cookie('Baked cookie', 'Tree') cookie3.bake() # This Baked cookie, is being baked with the shape Tree and chips of Chocolate Enjoy your cookie!
The 4 Pillars of OOP in Python
Object-Oriented Programming includes four main pillars:
Abstraction hides the internal functionality of an application from the user. The user could be either the end client or other developers.
We can find abstraction in our daily lives. For example, you know how to use your phone, but you probably don’t know exactly what’s happening inside it each time you open an app.
Another example is Python itself. You know how to use it to build functional software, and you can do it even if you don’t understand Python’s inner workings.
Applying the same to code allows you to collect all the objects in a problem and abstract standard functionality into classes.
Inheritance allows us to define multiple subclasses from an already defined class.
The primary purpose of it is to follow the DRY principle. You’ll be able to reuse a lot of code by implementing all the sharing components into superclasses.
You can think of it as the real-life concept of genetic inheritance. Children (subclass) are the result of inheritance between two parents (superclasses). They inherit all the physical characteristics (attributes) and some common behaviors (methods).
Polymorphism lets us slightly modify methods and attributes of the subclasses previously defined in the superclass.
The literal meaning is “many forms.” That’s because we build methods with the same name but different functionality.
Going back to the previous idea, children are also a perfect example of polymorphism. They can inherit a defined behavior get_hungry() but in a slightly different way, for instance, getting hungry every 4 hours instead of every 6.
Encapsulation is the process in which we protect the internal integrity of data in a class.
Although there isn’t a private statement in Python, you can apply encapsulation by using mangling in Python. There are special methods named getters and setters that allow us to access unique attributes and methods.
Let’s imagine a Human class that has a unique attribute named _height. You can modify this attribute only within certain constraints (it’s nearly impossible to be higher than 3 meters).
Build an Area Shape Resolver Calculator
One of the best things about Python is that it lets us create a wide variety of software, from a CLI (command-line interface) program to a complex web app.
Now that you’ve learned the pillar concepts of OOP, it’s time to apply them to an actual project.
Your task is to create an area calculator of the following shapes:
Shape Base Class
Firstly, create a file calculator.py and open it. Since we already have the objects to work with, it’ll be easy to abstract them in a class.
You can analyze the common characteristics and find out that all of these are 2D shapes. Therefore, the best option is to create a class Shape with a method get_area() from which each shape will inherit.
Note: All the methods should be verbs. That’s because this method is named get_area() and not area().
class Shape: def __init__(self): pass def get_area(self): pass
The code above defines the class; however, there isn’t anything interesting in it yet.
Let’s implement the standard functionality of most of these shapes.
class Shape: def __init__(self, side1, side2): self.side1 = side1 self.side2 = side2 def get_area(self): return self.side1 * self.side2 def __str__(self): return f'The area of this self.__class__.__name__ is: self.get_area()'
Let’s break down what we’re doing with this code:
- In the __init__ method, we’re requesting two parameters, side1 and side2. These will remain as instance attributes.
- The get_area() function returns the area of the shape. In this case, it’s using the formula of area of a rectangle since it’ll be easier to implement with other shapes.
- The __str__() method is a “magic method” just as __init__(). It allows you to modify the way an instance will print.
- The self.__class__.__name__ hidden attribute refers to the name of the class. If you were working with a Triangle class, that attribute would be “Triangle.”
Since we implemented the formula of area of the Rectangle, we could create a simple Rectangle class that does nothing but inherit from the Shape class.
To apply inheritance in Python, you’ll create a class as usual and surround the superclass you want to inherit from by parenthesis.
# Folded base class class Shape: ... class Rectangle(Shape): # Superclass in Parenthesis pass
We can take an excellent approach to polymorphism with the Square class.
Remember that a square is just a rectangle whose four sides are all equal. That means we can use the same formula to get the area.
We can do this by modifying the init method, accepting only a side as a parameter, and passing that side value to the constructor of the Rectangle class.
# Folded classes class Shape: ... class Rectangle(Shape): ... class Square(Rectangle): def __init__(self, side): super().__init__(side, side)
As you can see, the super function passes the side parameter twice to the superclass. In other words, it’s passing side both as side1 and side2 to the previously defined constructor.
A triangle is half as big as the rectangle that surrounds it.
Therefore, we can inherit from the Rectangle class and modify the get_area method to match the triangle area formula, which is one-half of the base multiplied by the height.
# Folded classes class Shape: ... class Rectangle(Shape): ... class Square(Rectangle): ... class Triangle(Rectangle): def __init__(self, base, height): super().__init__(base, height) def get_area(self): area = super().get_area() return area / 2
Another use case of the super() function is to call a method defined in the superclass and store the result as a variable. That’s what’s happening inside the get_area() method.
You can find the circle area with the formula πr², where r is the circle’s radius. That means we have to modify the get_area() method to implement that formula.
Note: We can import the approximate value of π from the math module
# Folded classes class Shape: ... class Rectangle(Shape): ... class Square(Rectangle): ... class Triangle(Rectangle): … # At the start of the file from math import pi class Circle(Shape): def __init__(self, radius): self.radius = radius def get_area(self): return pi * (self.radius ** 2)
The code above defines the Circle class, which uses a different constructor and get_area() methods.
Although Circle inherits from the Shape class, you can redefine every single method and attribute it to your liking.
Regular Hexagon Class
We only need the length of a side of a regular hexagon to calculate its area. It’s similar to the Square class, where we only pass an argument to the constructor.
However, the formula is quite different, and it implies the use of a square root. That’s why you’ll use the sqrt() function from the math module.
# Folded classes class Shape: ... class Rectangle(Shape): ... class Square(Rectangle): ... class Triangle(Rectangle): … class Circle(Shape): … # Import square root from math import sqrt class Hexagon(Rectangle): def get_area(self): return (3 * sqrt(3) * self.side1 ** 2) / 2
Testing Out Our Classes
You can enter an interactive mode when running a Python file using a debugger. The simplest way to do this is by using the built-in breakpoint function.
Note: This function is only available in Python 3.7 or newer.
from math import pi, sqrt # Folded classes class Shape: ... class Rectangle(Shape): ... class Square(Rectangle): ... class Triangle(Rectangle): … class Circle(Shape): … class Hexagon(Rectangle): … breakpoint()
Now, run the Python file and play around with the classes you created.
$ python calculator.py (Pdb) rec = Rectangle(1, 2)(Pdb) print(rec) The area of this Rectangle is: 2 (Pdb) sqr = Square(4) (Pdb) print(sqr) The area of this Square is: 16 (Pdb) tri = Triangle(2, 3) (Pdb) print(tri) The area of this Triangle is: 3.0 (Pdb) cir = Circle(4) (Pdb) print(cir) The area of this Circle is: 50.26548245743669 (Pdb) hex = Hexagon(3) (Pdb) print(hex) The area of this Hexagon is: 23.382685902179844
Create a class with a method run where the user can choose a shape and calculate its area.
When you’ve completed the challenge, you can send a pull request to the GitHub repo or publish your solution in the comment section.
In this article, you’ve learned about:
- The concept of object-oriented in Python
- Advantages of object-oriented over structured programming
- Basics of object-oriented programming in Python
- Concept of classes and how to use them in Python
- The constructor of a class in Python
- Methods and attributes in Python
- The four pillars of OOP
- Implementing abstraction, inheritance, and polymorphism in a project
Now it’s over to you!
Let us know your solution to the challenge below in the comments! And don’t forget to check out our comparison guide between Python and PHP.
If you enjoyed this article, then you’ll love Kinsta’s WordPress hosting platform. Turbocharge your website and get 24/7 support from our veteran WordPress team. Our Google Cloud powered infrastructure focuses on scalability, performance, and security. Let us show you the Kinsta difference! Check out our plans