Encapsulation

Part of the free LogicWiz Python course, Module 9: Object-oriented programming. The examples and practice run in the app.

Kabir's code ran wallet.balance = -5000 and Canteen Counter happily said he owed nothing. An object should protect its own rules, so outsiders can't push it into nonsense.

You'll learn to

  • Name the four pillars of OOP and what each one means
  • Use public, `_protected` and `__private` names, and explain name mangling
  • Guard attributes with `@property` getters and setters

Learn

The four pillars of OOP

Every OOP interview starts here.

Pillar In one line Lesson
Encapsulation Keep data and its rules together; control access 9.3
Inheritance A new class reuses and extends an old one 9.4
Polymorphism One call, different behaviour per class 9.5
Abstraction Show what an object does, hide how 9.5

Today's pillar is encapsulation: the object, not the outside world, decides what values are allowed.

Public, _protected, __private

Python has no truly private attributes. It uses naming conventions instead:

  • name: public. Anyone may use it.
  • _limit (one underscore): protected by convention. "Internal, please don't touch." Python does not stop you.
  • __balance (two underscores in front): private. Python renames it to _Wallet__balance. This is name mangling, and it makes accidental access (and clashes in subclasses) unlikely.

Name mangling in action

class Wallet:
    def __init__(self, owner, balance):
        self.owner = owner          # public
        self._limit = 2000          # protected: a polite "keep out"
        self.__balance = balance    # private: name-mangled

    def show(self):
        return f"{self.owner}: Rs {self.__balance}"

w = Wallet("Meher", 500)
print(w.show())
print(w._limit)
try:
    print(w.__balance)
except AttributeError as e:
    print("AttributeError:", e)
print(w._Wallet__balance)    # the mangled name still works

Inside the class, self.__balance works normally. Outside, the name has become _Wallet__balance.

Name mangling is about avoiding accidents, not security. Anyone who knows the mangled name can still reach it. Real protection comes from giving the class methods that check every change, and properties make that feel natural.

@property: an attribute with a guard

In Java you would write getPrice() and setPrice(). In Python you keep the nice item.price syntax and add the checks behind it:

  • @property above def price(self) runs that method whenever someone reads item.price.
  • @price.setter above a second def price(self, value) runs whenever someone assigns item.price = ....

The real value lives in a different name, usually self._price.

A price that refuses to go negative

class Item:
    def __init__(self, name, price):
        self.name = name
        self.price = price        # this goes through the setter below

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        if value <= 0:
            raise ValueError("price must be positive")
        self._price = value

    @property
    def with_gst(self):           # read-only and computed
        return round(self._price * 1.05, 2)

samosa = Item("Samosa", 20)
print(samosa.price, samosa.with_gst)
samosa.price = 30
print(samosa.price, samosa.with_gst)
try:
    samosa.price = -5
except ValueError as e:
    print("Rejected:", e)

Try samosa.with_gst = 50. There is no setter, so Python refuses with an AttributeError.

Inside the setter, store the value in self._price, never self.price. Writing self.price = value calls the setter again, which calls it again… until RecursionError. Also, the getter and setter must have the same name as the property.

Interviewers love "Does Python have private variables?" Answer: no truly private ones. _x is a convention; __x is name-mangled to _ClassName__x; and @property controls how an attribute is read and written.

In one line

_x means internal, __x is name-mangled to _Class__x; @property (plus @x.setter) guards an attribute while keeping obj.x syntax.

Interview corner

What are the four pillars of OOP?

Encapsulation (bundle data with methods and control access), Abstraction (expose what an object does, hide how), Inheritance (a class reuses and extends another), and Polymorphism (the same call behaves differently for different types). Give a one-line example of each, like @property for encapsulation and len() working on lists and strings for polymorphism.

Does Python have private variables?

Not truly. A single leading underscore (_x) is a convention meaning internal. A double leading underscore (__x) triggers name mangling to _ClassName__x, which prevents accidental access and subclass clashes but can still be reached deliberately. Python relies on "we are all consenting adults".

Why use `@property` instead of `get_x()`/`set_x()` methods?

A property keeps the simple attribute syntax (obj.price = 30) while running validation or computation behind it. You can start with a plain attribute and turn it into a property later without changing any calling code. Getters and setters everywhere are considered un-Pythonic.