What is encapsulation and how to implement it on variable and method in python
Tags: python, programming, tutorial
Published: 2023-08-17 17:37:52

Encapsulation is a fundamental concept in object-oriented programming (OOP) that involves bundling the data (attributes) and methods (functions) that operate on the data into a single unit, called a class. It provides a way to control the access to the data and methods, ensuring that the internal state of an object is hidden from the outside world and can only be accessed through well-defined interfaces.

## The key goals of encapsulation are:

1. **Data Hiding:** Restricting direct access to the internal state of an object, allowing controlled access through methods. This prevents unintended modifications or misuse of the data.

2. **Abstraction:** Presenting a simplified interface to the outside world while hiding complex implementation details. This makes code easier to understand and use.

In Python, encapsulation is achieved by using access modifiers and property decorators.

**Access Modifiers:**
Python doesn't have traditional access modifiers like other languages (e.g., `public`, `private`, `protected`). However, it uses naming conventions to indicate the intended visibility of attributes and methods.

1. `_single_underscore`: Indicates that an attribute or method is meant to be treated as a non-public part of the API and is for internal use only. It's a weak indication of encapsulation.

2. `__double_underscore`: Performs name mangling, making the attribute or method more difficult to access directly from outside the class.

**Property Decorators:**
Python also provides property decorators to create controlled access to attributes. This allows you to define custom behaviors when getting and setting attribute values.

Here's an example of encapsulation in Python:

```python
class Student:
    def __init__(self, name, age):
        self._name = name  # Single underscore indicates non-public, but not enforced
        self.__age = age   # Double underscore performs name mangling

    # Getter property for age
    @property
    def age(self):
        return self.__age

    # Setter property for age
    @age.setter
    def age(self, new_age):
        if new_age > 0:
            self.__age = new_age
        else:
            print("Age cannot be negative")

    def display(self):
        print(f"Name: {self._name}, Age: {self.__age}")

# Creating a Student object
student = Student("Alice", 20)

# Accessing attributes using methods
student.display()       # Output: Name: Alice, Age: 20
print(student._name)    # Output: Alice (non-public access)
print(student.__age)    # Error (name mangling changes the attribute name)
print(student.age)      # Output: 20 (using getter property)

# Modifying age using setter property
student.age = 21        # Setter called, age updated
student.display()       # Output: Name: Alice, Age: 21
student.age = -5        # Age cannot be negative
```

In this example, the `_name` attribute is indicated as non-public using a single underscore, but it's not strictly enforced. The `__age` attribute is name-mangled to `_Student__age`, making it harder to access from outside the class. The `age` attribute is accessed and modified using getter and setter property decorators, providing controlled access to the attribute.


In Python, you can achieve protected and private access control for methods using naming conventions. While Python doesn't enforce strict access modifiers like some other languages, it relies on naming conventions to indicate the intended visibility of methods. Here's how you can create protected and private methods:

1. **Protected Methods:**
   To indicate a method as protected, you can use a single underscore (`_`) prefix. While this doesn't actually prevent access, it's a convention that signals to other programmers that the method is intended for internal use within the class or its subclasses.

```python
class MyClass:
    def _protected_method(self):
        print("This is a protected method")

class MySubclass(MyClass):
    def access_protected(self):
        self._protected_method()

obj = MySubclass()
obj.access_protected()   # Output: This is a protected method
obj._protected_method()  # This can be accessed, but it's recommended not to do so
```

2. **Private Methods:**
   To indicate a method as private, you can use a double underscore (`__`) prefix. This performs name mangling, making the method name less likely to clash with names in subclasses or other classes.

```python
class MyClass:
    def __private_method(self):
        print("This is a private method")

class MySubclass(MyClass):
    def access_private(self):
        self.__private_method()

obj = MySubclass()
obj.access_private()   # Output: This is a private method
# obj.__private_method()  # This would cause an AttributeError due to name mangling
```

Remember that these naming conventions are just conventions, not strict access control mechanisms. It's still possible to access both protected and private methods from outside the class if you really want to, but following these conventions helps communicate the intended visibility and usage of methods.

In summary, you can use a single underscore (`_`) for protected methods and a double underscore (`__`) for private methods, but Python trusts developers to respect these conventions and use them responsibly.

Encapsulation helps in creating more maintainable and secure code by controlling access to data and methods, abstracting away implementation details, and enforcing proper usage of attributes.