← Back to Blog

Encapsulation: Control Over Your Data

The problem...

You have an Animal object. Status starts as "available". At some point in your code — or someone else's — this happens:

lassie.status = "missing"
lassie.age = -5

Python doesn't complain. The object accepts anything. A negative age. An invalid status. Data that breaks your logic silently.

With the procedural approach, you validated input at the function level. With OOP, the object itself can take responsibility for its own data.

The idea!

Encapsulation means controlling access to an object's data. Instead of allowing direct modification from outside, you define clear channels — methods — through which data can be read and changed. The object enforces its own rules.

Private attributes

In Python, a single underscore prefix signals that an attribute is intended for internal use only:

class Animal:
    def __init__(self, name, age):
        self.name  = name
        self._age  = age        # convention: treat as private

A double underscore makes it harder to access from outside — Python mangles the name:

class Animal:
    def __init__(self, name, age):
        self.name  = name
        self.__age = age        # name-mangled — harder to access directly
lassie = Animal("Lassie", 4)
print(lassie.__age)         # AttributeError
print(lassie._Animal__age)  # 4 — still accessible, but clearly not intended

Python doesn't have true private attributes like some other languages. The convention is the protection. Single underscore: "please don't touch this directly." Double underscore: "I really mean it."

Getters and setters

Instead of accessing attributes directly, you define methods that read and write them — with validation built in.

class Animal:
    VALID_STATUSES = ["available", "adopted"]
    VALID_HEALTH   = ["healthy", "ill"]

    def __init__(self, name, species, age):
        self.name     = name
        self.species  = species
        self._age     = age
        self._status  = "available"
        self._health  = "healthy"

    def get_age(self):
        return self._age

    def set_age(self, age):
        if isinstance(age, int) and age > 0:
            self._age = age
        else:
            print("Invalid age. Must be a positive integer.")

    def get_status(self):
        return self._status

    def set_status(self, status):
        if status in self.VALID_STATUSES:
            self._status = status
        else:
            print(f"Invalid status. Choose from: {self.VALID_STATUSES}")

    def get_health(self):
        return self._health

    def set_health(self, health):
        if health in self.VALID_HEALTH:
            self._health = health
        else:
            print(f"Invalid health. Choose from: {self.VALID_HEALTH}")
lassie = Animal("Lassie", "dog", 4)

lassie.set_age(5)
print(lassie.get_age())         # 5

lassie.set_age(-3)              # Invalid age. Must be a positive integer.
print(lassie.get_age())         # 5 — unchanged

lassie.set_status("adopted")
print(lassie.get_status())      # adopted

lassie.set_status("missing")    # Invalid status. Choose from: ['available', 'adopted']
print(lassie.get_status())      # adopted — unchanged

The object protects itself. Invalid data is rejected before it reaches the attribute.

Why this matters

In the Safe Paws project, status validation happened in the function that handled adoption. If you called a different function — or modified the dictionary directly — nothing stopped you from setting an invalid status.

With encapsulation, the validation lives inside the object. It doesn't matter how you try to change the status — the object enforces the rules every time.

Heads up!

  • Single underscore _attr — convention for "private", still accessible
  • Double underscore __attr — name mangling, harder but not impossible to access
  • Python has no true private attributes — convention is the protection
  • Getters and setters add validation — direct attribute access bypasses it

The mindset shift

Stop thinking: "I'll validate data wherever I use it."

Start thinking: "The object is responsible for its own data. It validates itself."

What you should understand now

  • Encapsulation means controlling access to an object's data
  • Single underscore signals "private by convention"
  • Double underscore applies name mangling — harder to access directly
  • Getters read private attributes, setters write them with validation
  • The object enforces its own rules — regardless of where it's used
[ login to bookmark ] // copied! 34 views · 2 min
// resources
Code Example oop_encapsulation.py
← prev Understanding OOP self next → Reusing Code with Inheritance
// 0 comments
// No comments yet. Be the first.
// leave a comment

// Your comment will appear after approval.