22
Introduction to Object Oriented Programming (OOP) using Python
Object Oriented Programming (OOP) is a programming paradigm that provides ways of structuring programs so that properties and behaviors are grouped into individual objects.
For example, an object could represent a person with properties such as name, age, and address, behaviors such as walking, talking, breathing, and running. It could represent an email with properties like a recipient list, subject, body, and behaviors like adding and sending attachments.
In other words, Object Oriented Programming is an approach to modeling concrete things in the real world, such as cars, as well as relationships between things, such as companies and employees, students and teachers, etc. This type of Programming models real-world entities as software objects that have some associated data and can perform certain functions.
Another common programming paradigm is procedural programming, which structures a program like a recipe in the sense that it provides a set of steps, in the form of functions and blocks of code, that flow sequentially to complete a task.
The most important point is that objects are at the center of Object Oriented Programming, not only representing the data, as in procedural programming, but also in the general structure of the program.
Developers often choose to use Object Oriented Programming(OOP) in their Python programs because it makes code more reusable and makes it easier to work with larger programs. Such programs help the developer avoid repeating code because a class can be defined once and reused many times.
Python is an Object Oriented Programming(OOP) language which is a very popular way of creating software. Unlike procedural programming, where the main emphasis is on functions, object-oriented programming emphasizes objects.
With OOP you can make your program much more organized, scalable, reusable and extensible. However, the concept of Object Oriented Programming(OOP) may seem a bit strange to some developers. It may be difficult to understand, but I assure you that it is, without a doubt, a very powerful concept.
Let's talk a little about some elements that make up the essence of Object Oriented Programming(OOP).
Just as function definitions begin with the def keyword in Python, class definitions begin with a class keyword.
The first string inside the class is called docstring and has a short description about the class. Although it is not mandatory, it is highly recommended to put it for documentation reasons in general, but do not worry that nothing happens if you do not put it, it is only to do things in the best possible way.
Here is a simple class definition, nothing fancy, something simple already with the docstring included so you can see how it is added to our code.
class NicaraguanTypicalMeals:
''' This is the NicaraguanTypicalMeals class '''
pass
A class creates a new local namespace where all its attributes are defined. Attributes can be data or functions.
It also contains special attributes that start with two underscores . To give you a concrete example of this you can try with the __doc which gives us the documentation string of some class.
As soon as we define a class, a new class object with the same name is created. This class object allows us to access the different attributes, as well as to create instances of new objects of that class.
An example of classes that also includes a method which will be used to greet the Python dev, would be something like the one proposed in the following snippet:
class PythonDev:
''' This is the docstrings for the PythonDev class '''
def __init__(self, age=18, nationality="Nicaraguense"):
self.age = age
self.nationality = nationality
def greet(self):
print(f"Nationality: {self.nationality}.")
print(f"Current age: {self.age} years old.")
# First, we create the instance of this class
pythonDev01 = PythonDev(39)
# Let's use the methods of this object
pythonDev01.greet()
# Create another instance passing both parameters now
pythonDev02 = PythonDev(20, "Costarricense")
pythonDev02.greet()
# We can also access your methods individually
print(f"Nationality: {pythonDev01.nationality}")
print(f"Current Age: {pythonDev01.age}")
Classes are used to create user-defined data structures. Classes define functions called methods, which identify the behaviors and actions that an object created from that class can perform with its data. Let's see an example of a class:
class Dog:
def __init__(self, name, age, breed):
self.name = name
self.age = age
self.breed = breed
# Now let's instantiate by creating two objects of this class
chele = Dog("Chele", 4, "Belgian Tervuren")
kiki = Dog("Kiki", 2, "Doberman Pinscher")
# Let's see then what these Dog instances contain:
# Chele is a 4 years old Belgian Tervuren.
print(f"{chele.name} is a {chele.age} years old {chele.breed}.")
# Kiki is a 2 years old Doberman Pinscher.
print(f"{kiki.name} is a {kiki.age} years old {kiki.breed}.")
As you can see in this example, a class is a model of how something should be defined. It doesn't actually contain any data. The Dog class specifies that a name, an age, and a breed are required to define a dog, but it does not contain the name, age, or breed of any specific dog.
While the class is the blueprint, an instance is an object that is constructed from a class and contains actual data. An instance of the Dog class is no longer a blueprint. He is a real dog with a name, like Chele, who is four years old and is a Belgiam Tervuren breed.
In other words, a class is like a form or quiz. An instance is like a form that has been filled with information. Just as many people can complete the same form with their own unique information, many instances can be created from a single class, Do you understand me, Bambi?
In Python, developers can define objects. An object is a collection of methods and variables, which live somewhere in the computer's memory. These objects can be manipulated at run time.
In OOP, an object is an instance of a class. As I said before, a class is like a plan, while an instance is a copy of the class with real values. The objects are made up of the following elements:
- State: it is represented by the attributes of an object. It also reflects the properties of an object.
- Behavior: it is represented by the methods of an object. It also reflects the response of an object to other objects.
- Identity: gives an object a unique name and allows an object to interact with other objects.
When an object of a class is created, an instance of the class is said to be created. All instances share the attributes and behavior of the class. But the values of those attributes, that is, the state, are unique for each object. A single class can have any number of instances.
To show you a bit of this, let's create a theoretical example a little out of the ordinary, talking about popular Nicaraguan foods as attribute values of this class. Then we will define an object of the NicaraguanTypicalMeals class.
To create an object, let's declare the class from which we inherit the characteristics of this instance. Then we will access those inherited properties by printing each one of them to the screen.
In Python, just a few lines of code are enough to do this:
class NicaraguanTypicalMeals:
''' This is the NicaraguanTypicalMeals Class '''
# Declare the class attributes with their respective values
breakfast = "Nacatamal o Tamuga de Masatepe"
lunch = "Sopa de Mondongo Masatepina"
dinner = "GalloPinto con Queso Frito y Tajadas Maduras"
# We now create the instance of this class
dish = NicaraguanTypicalMeals()
# Let's see what the created object owns as
# the primary inheritance of the class
print(f"Breakfast: {dish.breakfast}.")
print(f"Lunch: {dish.lunch}.")
print(f"Dinner: {dish.dinner}.")
As we can see, a class is a model for that NicaraguanTypicalMeals object that we just built. We can think of a class as a sketch (prototype) of a house. It contains all the details about the floors, doors, windows, etc. Based on these descriptions we build the house. The house is the object.
Just as many houses can be made from the plan of a house, we can create many objects from a class. An object is also called an instance of a class and the process of creating this object is called instantiation and you will hear it a lot in the OOP, so I recommend you remember this well, dear reader.
We saw that the class object could be used to access different attributes. It can also be used to create new instances of objects, which we also know as instantiation of that class, which we mentioned earlier. The procedure for creating an object is similar to calling a function.
PythonZealot01 = PythonZealot()
This will create a new object instance called PythonZealot01. We can access the attributes of the objects using the prefix of the object name.
The attributes can be data or methods. The methods of an object are corresponding functions of that class. This means that since PythonZealot01.sayHello is a function object (or a class attribute) then PythonZealot01.sayHello will be a method object.
class PythonZealot:
''' This is the docstrings for the PythonZealot class '''
# Declare the class attributes with their respective values
job_title = "Python Developer"
def sayHello(self):
print(f"Hi guys: I am a {self.job_title}.")
# First, create an instance of this class
PythonZealot01 = PythonZealot()
# Use the object methods now
PythonZealot01.sayHello()
In Python, getters and setters are not the same as in other object-oriented programming languages. Basically, the main purpose of using getters and setters in object-oriented programs is to ensure data encapsulation. Private variables in Python are not actually hidden fields like in other object-oriented languages. Generally, Getters and Setters in Python are often used when:
- We need to add validation logic to get and set a value.
- To avoid direct access to a class field, that is, private variables cannot be directly accessed or modified by an external user. But what is a Getter and a Setter, in short? Let's see then how we can define each of these methods theoretically speaking:
Getters: These are the methods used in Object Oriented Programming (OOP) that helps to access the private attributes of a class.
Setters: These are the methods used in Object Oriented Programming (OOP) that help to set the value of private attributes in a class.
Let's now see how we can quickly proceed to implement a private class attribute using python for this:
class Developer:
''' This is the Developer Class '''
def __init__(self, name, age):
# declare attributes or properties as private
self.__name = name
self.__age = age
# getter method to get the properties using an object
def get_name(self):
return self.__name
# setter method to change the value 'name' using an object
def set_name(self, name):
self.__name = name
In this example we clearly see that the Developer class has three methods.
_init _: - It is used to initialize the attributes or properties of a class.
__name: This is a private attribute.
get_name: It is used to obtain the values of the private attribute name.
set_name: It is used to set the value of name using an object of a class.
You cannot access private variables directly in Python. That is why you implemented the getter method.
Still confused about the functionality of the class? Let me explain this to you briefly and don't worry if you don't understand everything because it takes some time to savor it but it is worth doing, my dear reader.
class Developer:
''' This is the Developer Class '''
def __init__(self, name):
# declare attributes or properties as private
self.__name = name
# getter method to get the properties using an object
def get_name(self):
return self.__name
# setter method to change the value 'name' using an object
def set_name(self, name):
self.__name = name
# --------------- Implementation -------------------
# Creating an object based on the WebDeveloper class
objWebDev = WebDeveloper("Alvison Hunter")
# Get the value of 'name' using the get_name() method
print(f"Name: {objWebDev.get_name()}")
# Set a new value for 'name' attribute using set_name() method
objWebDev.set_name("Alvison Lucas Hunter Arnuero")
# Let's see the results now
print(f"Name: {objWebDev.get_name()}")
This is how you implement private attributes, getters, and setters in Python. Let's write the same implementation but in a Pythonic way.
In the Pythonic way, no getters or setters method is needed to access or change attributes. You can access it directly using the name of the attributes.
Are you telling me that the Pythonic way is even easier without having to use getters and setters? Hmm… not exactly, my dear colleague. Let's see this with the following example to clarify this question.
class PythonicWay:
''' This is the PythonicWay class '''
def __init__(self, url):
self.url = url
# Create an object or instance for the PythonicWay class
obj2 = PythonicWay("https://alvisonhunter.com/")
# Let's see the results now, fellows
print(f"Developer Website: {obj2.url}")
Developer hides private attributes and methods. It implements the encapsulation function that is typical of Object-Oriented Programming (OOP).
In contrast, the PythonicWay class does not hide the data. It does not implement any Object Oriented Programming (OOP) encapsulation functions.
What is the most recommended way to use in this type of scenario for any modern developer?
To be honest, it all depends on our need when solving the problem. If you want private attributes and methods, you can implement the class using the setters and getters methods for them, otherwise you can implement it in the Pythonic way.
We'll do it right now: What if you want to have some conditions to set the value of an attribute in the Developer class?
Let's say that if the value we pass for the name attribute is equal to the string "Alvison Hunter", we can set it to the attribute; otherwise, set the default value us directly in this method.
Let's implement this by changing the set_name() method in Developer. Let's review the class for this by creating an object and once we have the instance we will try to make this change.
class Developer:
''' This is the docstring for the Developer class '''
def __init__(self, name, age):
# Declare attributes or properties as private
self.__name = name
self.__age = age
# getter method to get the properties using an object
def get_name(self):
return self.__name
# setter method to change the value 'name' using an object
def set_name(self, name):
self.__name = name
# getter method to get the age property of the objec
def get_age(self):
return self.__age
# Add the option to set the age property only if the
# parameter sent is a positive number and not a string
def set_age(self, age):
# Expected condition to see if the parameter is a valid integer
if not isinstance(age, int):
print("You entered an invalid age. Setting value to default.")
self.__age = 18
else:
self.__age = age
# --------------- Implementation -------------------
# Create an object based on the Developer class
obj = Developer("Alvison Hunter", 40)
# Get the value of 'name' using the get_name() method
print(f"Name: {obj.get_name()}")
# Get the value of 'age' using the get_age() method
print(f"Age: {obj.get_age()}")
# set a new value for 'name' attribute using set_name()
obj.set_name("Alvison Lucas Hunter Arnuero")
# set a new value for the 'age' attribute with the set_age() method
obj.set_age("35")
# Let's see the results now for both cases
print(f"Name: {obj.get_name()} | Age: {obj.get_age()}")
Everything looks good, it works correctly, however there is a more appropriate way to do this. Let's see how to implement the above class using the @property decorator.
NOTE: I will write about decorators in the classes in future articles but for now we review the property decorator. If you want more information about decorators, I invite you to watch this video where it is explained in a simple way how to use them with Python.
class Property:
def __init__(self, language):
# Starting the attribute
self.language = language
@property
def language(self):
return self.__language
# The attribute & method name must be the same as
#the one used to set the attribute value
@language.setter
def language(self, language):
if language.title() == "Python":
self.__language = language.title()
else:
self.__language = "Use Python, dude!"
obj3 = Property("JavaScript")
print(obj3.language)
In this example, we can see that @property is used to get the value of a private attribute without using any getter. It is important to mention that we have to put an @property line in front of the method where we return the private variable.
To set the value of the private variable, we use the form of @method_name.setter in front of the method. We have to use it as our setter.
@language.setter will set the value of language by checking the conditions we mentioned in the method. Another way to use the property is as follows:
class PropertyAlternative:
def __init__(self, new_lang):
# call set_lang() to set the value 'lang'
# checking certain conditions first
self.__set_lang(new_lang)
# getter method to get the properties using an object
def __get_lang(self):
return self.__lang
# setter method to change the 'lang' value using an object
def __set_lang(self, new_lang):
# condition to check if new_lang is appropriate or not
if new_lang.title() == "Python":
self.__lang = new_lang
else:
self.__lang = "Get Pythonized, young lad!"
#Pass the getter & setter methods to property() method
# Let's assign the result to a variable as class attribute
language = property(__get_lang, __set_lang)
# create a new object of the PropertyAlternative class
obj4 = PropertyAlternative("Java")
print(f"Lenguaje: {obj4.language}")
In this example we see that all the getter and setter methods are passed to the property method and then the result is assigned to a variable that must be used as a class attribute, which in this case we call language.
It should be noted that the setter and getter methods have to be private to hide them as shown in the example.
Well, I think we have already managed to cover a little about Object Oriented Programming and we also managed to implement encapsulation in Python in a basic way and at the same time we have captured in a simple way the difference between the use of setter, getter and the property decorator when we create Our classes.
It is worth mentioning that most modern programming languages, such as Java, C # and C ++, among others, follow the principles of Object-Oriented Programming (OOP), so what is mentioned here will also be, to some extent, applicable to some of them no matter where you are currently going with respect to your career or whatever your preferred programming language is.
NOTE: Our YouTube Channel is always at your service, colleagues! If you are starting with Python or JavaScript, or if you simply like web programming in general, I recommend that you take a look at my YouTube Channel where I share tips on these languages, as well as many examples with code included which we share in a way free to help you on this beautiful path of software development.
❤️ If you enjoyed this article, that motivates to write more!
🦄 If you truly consider this article helped you wholly!
🔖 If you need to check back on this article later on.
🤔 Please leave your comment, your opinion very important.
See you next time, buddies! Don't forget to share this post among all of your friends!
22