Introduction to Object Oriented Programming (OOP) using Python
Tags: python, classes, object, objectorientedprogramming
Published: 2021-07-24 05:35:10

<strong>Object Oriented Programming (OOP)</strong> 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, <strong>Object Oriented Programming</strong> 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.

### Why should we use Object Oriented Programming (OOP)?

Developers often choose to use <strong>Object Oriented Programming(OOP)</strong> 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 <strong>Object Oriented Programming(OOP)</strong> 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 <strong>Object Oriented Programming(OOP)</strong> 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 <strong>Object Oriented Programming(OOP).</strong>

### How do we define classes with Python?

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.

```python
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:

```python
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 versus Instances
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:

```python
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?

### What are objects in Python?
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.

### Declaring objects (also called instantiating a class)
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 <strong>NicaraguanTypicalMeals</strong> 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:

```python
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 <strong>NicaraguanTypicalMeals</strong> 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.

### Creating an object in Python
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.

```python
PythonZealot01 = PythonZealot()
```

This will create a new object instance called <strong>PythonZealot01</strong>. 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 <strong>PythonZealot01.sayHello</strong> will be a method object.

```python
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()
```
### Using Properties, Getters and Setters in a class

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:

<b>Getters:</b> These are the methods used in Object Oriented Programming (OOP) that helps to access the private attributes of a class.

<b>Setters:</b> These are the methods used in Object Oriented Programming (OOP) that help to set the value of private attributes in a class.

### Private Attributes and Encapsulation in Objects

Let's now see how we can quickly proceed to implement a private class attribute using python for this:

```python
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.

```python
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 <strong>Pythonic way</strong>.

In the <strong>Pythonic way</strong>, 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.


```python
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}")

```

###### What is the difference between the two classes mentioned above?

Developer hides private attributes and methods. It implements the encapsulation function that is typical of Object-Oriented Programming (OOP).

In contrast, the <strong>PythonicWay</strong> 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.

####When are we going to talk about the @property decorator?

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.

```python
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.

<strong>NOTE:</strong> 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](https://www.youtube.com/watch?v=cOXSAY53DTI) where it is explained in a simple way how to use them with Python.

```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 <strong>@method_name.setter</strong> in front of the method. We have to use it as our setter.

<strong>@language.setter</strong> will set the value of language by checking the conditions we mentioned in the method. Another way to use the property is as follows:

```python
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.

#### Summary

Well, I think we have already managed to cover a little about <strong>Object Oriented Programming</strong> 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 <strong>Object-Oriented Programming (OOP)</strong>, 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.

<b>NOTE:</b> Our [YouTube Channel](https://bit.ly/3p9hpqj) 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.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5oojw2tdi7tna5mokwjv.png)

Thanks for reading this article, I hope you enjoyed it as much as I did writing it. Until next time, dear readers!

❤️ Your enjoyment of this article encourages me to write further.
💬 Kindly share your valuable opinion by leaving a comment.
🔖 Bookmark this article for future reference.
🔗 If this article has truly helped you, please share it.