Encapsulation in Python

This article provides a tutorial on encapsulation in Python, a fundamental concept in object-oriented programming.

Updated March 9, 2023

Hello future Python wizard, welcome to Python Help!

Today we will delve into the topic of encapsulation.

Encapsulation is the practice of hiding internal details of an object from the outside world, while providing a public interface for interacting with it. This can be achieved through the use of access modifiers like public, private, and protected.

In Python, there are no true access modifiers like in other programming languages, such as Java or C++. However, we can still achieve encapsulation through the use of conventions and techniques.

Let’s start with an example. Suppose we have a class called Person that has two attributes: name and age. We want to ensure that the age attribute is never negative.

Here’s one way we could implement it:

class Person:
    def __init__(self, name, age):
        self.name = name
        self._age = age
    
    def get_age(self):
        return self._age
    
    def set_age(self, age):
        if age < 0:
            raise ValueError("Age cannot be negative")
        self._age = age

Notice that we’ve prefixed the _age attribute with an underscore, indicating that it’s intended to be private. However, this is just a convention, and other parts of our code could still access it if they really wanted to. Nonetheless, by providing public get_age() and set_age() methods, we’re effectively encapsulating the age attribute.

Let’s test it out:

person = Person("Alice", 30)
print(person.get_age()) # Output: 30
person.set_age(-5) # Raises ValueError: Age cannot be negative

As expected, trying to set a negative age raises a ValueError.

Now, let’s take a look at another example. Suppose we have a class called BankAccount that has a balance attribute, and we want to prevent other parts of our code from directly modifying it. We could achieve this by making the balance attribute private, and providing a deposit() method to add funds to the account.

Here’s what the implementation could look like:

class BankAccount:
    def __init__(self, balance):
        self._balance = balance
    
    def deposit(self, amount):
        if amount < 0:
            raise ValueError("Deposit amount cannot be negative")
        self._balance += amount
    
    def get_balance(self):
        return self._balance

Again, we’re using the underscore convention to indicate that the balance attribute is intended to be private. By only providing a deposit() method to add funds to the account, we’re encapsulating the balance attribute.

Let’s test it out:


account = BankAccount(100)
account.deposit(50)
print(account.get_balance()) # Output: 150

account._balance = 200 # We shouldn't be doing this!
print(account.get_balance()) # Output: 200

Notice that even though we can technically access the private _balance attribute, doing so goes against the principle of encapsulation.

Summary

In summary, encapsulation is a powerful technique for managing complexity and improving code maintainability. By hiding internal details of an object from the outside world, we can reduce coupling and improve modularity. While Python doesn’t have true access modifiers, we can still achieve encapsulation through the use of conventions and techniques like we’ve seen in these examples.

Hey! Do you love Python? Want to learn more about it?
Let's connect on Twitter or LinkedIn. I talk about this stuff all the time!