Python Encapsulation
Tags: python, tutorial, programming, beginners
Published: 2022-03-05 10:18:06

> [Sign up to my newsletter!](https://baransel.dev/newsletter/).

## What is Python Encapsulation?

**Encapsulation** means restricting the access of methods and variables. We add the direct access and change restriction feature to methods or variables. So why are we doing this? Because the codes we write should not be changed or the values we change should be changed in a controlled manner. It is in these situations that encapsulation comes to our rescue like a magic wand. Now let's show with examples.

```python
class RegisterCourse:     
    def __init__(self):
        self.name = "Baransel"
        self.surname = "Arslan"
        self.exam1 = 82
        self.exam2 = 93

register = RegisterCourse()

print("Name : ", register.name)
print("Surname : ", register.surname) 
print("Exam 1 : ", register.exam1)
print("Exam 2 :", register.exam2)
```


We created a class called `RegisterCourse`. Now let's view the variables of our class

```
Name :  Baransel
Surname :  Arslan
Exam 1 :  82
Exam 2 : 93
```

There is only one problem here. We can access and even change this student's information from outside.

```
register.exam1 = 40
register.exam2 = 50
```

Let's look at the information again;

```
Name :  Baransel
Surname :  Arslan
Exam 1 :  40
Exam 2 : 50
```

As you can see, we can access and change the student's personal information. So, how can we restrict this person's information from being accessed and changed from outside?

## Using Python Encaplusation

Since we create the person's information globally, it is accessible to everyone. For this, we need to define our data and methods as private. Let's see how it's done.

It's a simple process, we just add `__` in front of the data we created, let's try it right away;

```python
class RegisterCourse:     
    def __init__(self):
        self.name = "Baransel"
        self.surname = "Arslan"
        self.__exam1 = 82
        self.__exam2 = 93

register = RegisterCourse()

print("Name : ", register.name)
print("Surname : ", register.surname) 
print("Exam 1 : ", register.exam1)
print("Exam 2 :", register.exam2)
```


We got an error like this, right?

`AttributeError: 'RegisterCourse' object has no attribute 'exam1'`

Since we made the exam information **private**, it gave an error because it could not reach the exam information. Now we can access these values only within this class. So how do we access it from other classes? Let's continue reading the article. So, can we do the same functions in methods? Let's show it with an example.

````python
class RegisterCourse:     
    def __init__(self):
        self.name = "Baransel"
        self.surname = "Arslan"
        self.__exam1 = 82
        self.__exam2 = 93
        self.courses = []
    def add(self, course):
        self.courses.append(course)

register = RegisterCourse()

register.add("Database Managment")
print("Name : ", register.name)
print("Surname : ", register.surname) 
print(register.courses)
```

As you can see, we can see the courses taken by the student and add courses. Now let's restrict external access and modification in this method.

```python
class RegisterCourse:     
    def __init__(self):
        self.name = "Baransel"
        self.surname = "Arslan"
        self.__exam1 = 82
        self.__exam2 = 93
        self.courses = []
    def __add(self, course):
        self.courses.append(course)

register = RegisterCourse()

register.add("Database Managment")
```

`AttributeError: 'RegisterCourse' object has no attribute 'add'`

Again we got a similar error. Although we have a method called `add()`, we cannot access it because there is no outside access (we did this by adding `__` to the beginning of the method).

Well, we can think of this. Ok, I have blocked my external access to `exam1` and `exam2` variables. Now I can't both read and change `exam1` and `exam2` variables. But I want to at least read the value, if I don't change it.

## External Access to Encapsulated Data

So how do we do this? We will do it with a structure similar to the **getter** and **setter** methods, which we often encounter in almost many programming languages, then let's try it.

```python
class RegisterCourse:     
    def __init__(self):
        self.name = "Baransel"
        self.surname = "Arslan"
        self.__exam1 = 82
        self.__exam2 = 93
        self.__courses = []

    def __add(self, course):
        self.__courses.append(course)
    
    def getExam1(self):
        return self.__exam1

register = RegisterCourse()

print("Name : ", register.name)
print("Surname : ", register.surname) 
print("Exam 1 : ", register.getExam1())
```

Let's take a look at the Output:

```
Name :  Baransel
Surname :  Arslan
Exam 1 :  82
```

As you can see, we have reached the `exam1` information in this way. By the way, I created the name `getExam1` and you can give it another name. Since this structure is a general structure, writing it this way (putting **get** at the beginning of the variable) can increase the readability of your code.

Well, let's say that even though the variable is private, I want to both access and change its value. We just added a **getter** method, now let's add a **setter** method.

```python
class RegisterCourse:     
    def __init__(self):
        self.name = "Baransel"
        self.surname = "Arslan"
        self.__exam1 = 82
        self.__exam2 = 93
        self.__courses = []

    def __add(self, course):
        self.__courses.append(course)
    
    def getExam1(self):
        return self.__exam1
    
    def setExam1(self, newValue):
        self.__exam1 = newValue

register = RegisterCourse()

print("Name : ", register.name)
print("Surname : ", register.surname) 
print("Exam 1 : ", register.getExam1())
```

Let's take a look at the output;

```
Name :  Baransel
Surname :  Arslan
Exam 1 :  82
```

As you can see the grade information has not changed. Because we didn't call the `setExam1()` function, let's call the `setExam1()` function we just created.

```python
register.setExam1(60)
```

Let's look at the output;

```
Name :  Baransel
Surname :  Arslan
Exam 1 :  60
```

This is how we call our function. You can now change private values with the setter function.

Well, I can't access it directly, but I was able to change the variable as I wanted with the setter method. Why bother with the getter setter? Why did I feel the need to make it private and use the setter method? Because when I access it directly, I can change the value of the variable without any checking. But when I use the setter function in this way, I can put the validations I want in it. For example, in direct access to `exam1` value, I can give – (negative) values, but when a negative value comes in the setter method, I can warn the user or throw an error without directly equating it to `exam1` value. Let's show it with an example;

```python
def setExam(self, newValue):
    if newValue<0 or newValue>100 :
        raise ValueError("Exam score cannot be less than 0 and greater than 100. ")
```

We wrote our setter function, now let's see our whole program.

```python
class RegisterCourse:     
    def __init__(self):
        self.name = "Baransel"
        self.surname = "Arslan"
        self.__exam1 = 82
        self.__exam2 = 93
        self.__courses = []

    def __add(self, course):
        self.__courses.append(course)
    
    def getExam1(self):
        return self.__exam1
    
    def setExam(self, newValue):
        if newValue<0 or newValue>100 :
            raise ValueError("Exam score cannot be less than 0 and greater than 100. ")
        self.__exam1 = newValue

register = RegisterCourse()
register.setExam(-10)
```

`ValueError: Exam score cannot be less than 0 and greater than 100.`

Here, we have checked whether the exam score is less than 0 and greater than 100.

## You might also like

[Python Functions](https://baransel.dev/post/python-functions/)
[Python File Operations](https://baransel.dev/post/python-file-operations/)
[Python Set and Frozenset](https://baransel.dev/post/python-set-and-frozenset/)
[Python dictionary and dictionary methods](https://baransel.dev/post/python-dictionary-and-dictionary-methods/)
[Python tuple and tuple methods](https://baransel.dev/post/python-tuple-and-tuple-methods/)