Kahibaro
Discord Login Register

Encapsulation

Understanding Encapsulation in Python

Encapsulation is about bundling data and the code that works with that data together, and controlling access to that data.

In Python classes, encapsulation usually means:

Why Encapsulation Is Useful

Encapsulation helps you:

  1. Protect data from accidental changes

You decide which parts of an object can be changed directly, and which should only be changed in controlled ways.

  1. Make code easier to understand

Other parts of the program do not need to know how something is done, only how to use it.

  1. Make changes without breaking everything

You can change how a class works on the inside, as long as its public interface (methods and attributes you promised) stays the same.

A common idea in encapsulation is: “Don’t expose more than you must.”

Public, “Protected”, and “Private” (Python Style)

Python does not enforce strict access control like some other languages. Instead, it uses naming conventions to signal how attributes and methods should be used.

Public Attributes and Methods

Example:

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner      # public
        self.balance = balance  # public
    def deposit(self, amount):  # public
        self.balance += amount

Code outside the class is “allowed” to use owner, balance, and deposit:

account = BankAccount("Alice", 100)
print(account.balance)   # allowed (public)
account.deposit(50)      # allowed (public)

“Protected” (Single Underscore)

Example:

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self._balance = balance  # "protected" by convention
    def deposit(self, amount):
        self._balance += amount
    def get_balance(self):
        return self._balance

You can still access _balance from outside, but you shouldn’t:

account = BankAccount("Alice", 100)
print(account.get_balance())  # recommended
print(account._balance)       # possible, but not recommended

“Private” (Double Underscore)

Example:

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance  # "private"
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
    def get_balance(self):
        return self.__balance

Trying to access __balance directly:

account = BankAccount("Alice", 100)
print(account.get_balance())   # OK
print(account.__balance)       # AttributeError

Internally, Python stored it as _BankAccount__balance; you could access it like this, but you should not in normal code:

print(account._BankAccount__balance)  # works, but bad practice

The idea is: double underscores strongly signal “don’t touch this from outside”.

Hiding Internal Details with Methods

Encapsulation is not just about underscores; it’s about controlling how data is changed.

Instead of letting code directly change attributes, you can:

Example: only allow valid ages for a person:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.__age = 0
        self.set_age(age)
    def set_age(self, age):
        if 0 <= age <= 120:
            self.__age = age
        else:
            print("Invalid age, keeping old value.")
    def get_age(self):
        return self.__age

Usage:

p = Person("Bob", 30)
print(p.get_age())  # 30
p.set_age(200)      # Invalid age, keeping old value.
print(p.get_age())  # still 30
# Direct access blocked:
# p.__age  -> AttributeError

The Person class encapsulates the age data and decides what values are allowed.

Encapsulation and “Information Hiding”

A typical pattern:

Example: a simple counter that other code can use but not mess up:

class Counter:
    def __init__(self):
        self.__value = 0  # internal state
    def increment(self):
        self.__value += 1
    def reset(self):
        self.__value = 0
    def get_value(self):
        return self.__value

From outside:

c = Counter()
c.increment()
c.increment()
print(c.get_value())  # 2
# There is no direct way (on purpose) to set the value to something random:
# c.__value = 999  # doesn't touch the internal value; creates a new attribute

Here:

Using Properties for Encapsulation

Python offers properties to make encapsulation more convenient. With a property, you can use attribute syntax (obj.x) but still have getter/setter logic behind the scenes.

Basic pattern:

Example:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.__age = 0
        self.age = age  # will use the property setter
    @property
    def age(self):
        # getter
        return self.__age
    @age.setter
    def age(self, value):
        # setter with validation
        if 0 <= value <= 120:
            self.__age = value
        else:
            print("Invalid age, keeping old value.")

Usage:

p = Person("Alice", 25)
print(p.age)   # uses getter -> 25
p.age = 30     # uses setter
print(p.age)   # 30
p.age = 200    # Invalid age, keeping old value.
print(p.age)   # still 30

From the outside, age looks like a simple attribute, but it is encapsulated with validation logic.

Practical Encapsulation Example: Bank Account

Putting it together:

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner          # public
        self.__balance = 0          # "private"
        self.deposit(balance)       # reuse method for validation
    @property
    def balance(self):
        # read-only property (no setter)
        return self.__balance
    def deposit(self, amount):
        if amount <= 0:
            print("Deposit must be positive.")
            return
        self.__balance += amount
    def withdraw(self, amount):
        if amount <= 0:
            print("Withdrawal must be positive.")
            return
        if amount > self.__balance:
            print("Insufficient funds.")
            return
        self.__balance -= amount

Usage:

account = BankAccount("Alice", 100)
print(account.balance)     # 100
account.deposit(50)
print(account.balance)     # 150
account.withdraw(200)      # Insufficient funds.
print(account.balance)     # 150
# account.balance = 1000   # AttributeError: no setter defined
# account.__balance        # AttributeError: private attribute

In this design:

Encapsulation and Good Class Design

When you design classes, ask:

  1. What should other code be allowed to do with this object?
    • These become public methods and attributes.
  2. What should other code not touch directly?
    • These become “private” or “protected” attributes/methods (using _ or __).
  3. Do I need validation or rules for changes?
    • Use methods or properties instead of exposing raw attributes.

Encapsulation helps you keep your classes clean, safe, and easier to use, and it becomes more and more important as your programs grow larger.

Views: 14

Comments

Please login to add a comment.

Don't have an account? Register now!