Home Blogs All Courses
python c OA ( Online Assessment ) Amazon Web Services (AWS) leetcode dsa algorithms java Machine learning
Get Referral
Login Sign Up

Encapsulation in Python: Private Variables, Getters, Setters & @property


Would you like anyone to directly change your bank balance to zero with one line of code? Of course not! Encapsulation is the OOP principle that protects an object's data and allows changes only through safe, controlled methods. In this blog you will learn public, protected and private members, name mangling, getters and setters, and the Pythonic @property decorator.

Watch the video first, then use this blog as your notes. Every section has runnable examples, and at the end you will find a "Predict the Output" quiz, practice challenges, a self-check list, interview questions and a quick summary.

How to use this blog: Open the Python shell (type python in your terminal) or any online Python editor and type every example yourself. Change the values, break the code and see what happens — that is how concepts really stick.

1. What is Encapsulation?

Encapsulation means bundling data (attributes) and the methods that work on that data into a single unit (a class), and restricting direct access to some of that data to protect its integrity.

  • Capsule analogy: a medicine capsule hides the powder inside; you just swallow it.
  • ATM analogy: you can't open the ATM and take cash; you use the keypad (methods) and the machine checks your PIN and balance.

Encapsulation vs Abstraction: Encapsulation is about hiding and protecting data (how access is controlled). Abstraction is about hiding complexity (showing only what is necessary). They work together.

2. The Problem Without Encapsulation

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance

acc = BankAccount("Riya", 1000)
acc.balance = -50000        # anyone can set ANY value - no validation!
print(acc.balance)          # -50000

3. Access Modifiers in Python

Python does not have public / private keywords like Java or C++. Instead it uses naming conventions with underscores.

TypeSyntaxAccessible fromMeaning
Publicself.nameAnywhereNormal attribute — free to use
Protectedself._nameClass and subclasses (by convention)"Internal use — please don't touch from outside"
Privateself.__nameOnly inside the className-mangled to prevent accidental access
class Employee:
    def __init__(self, name, department, salary):
        self.name = name                 # public
        self._department = department    # protected
        self.__salary = salary           # private

    def show(self):
        # all three are accessible INSIDE the class
        print(self.name, self._department, self.__salary)

e = Employee("Aman", "IT", 50000)
e.show()                 # Aman IT 50000
print(e.name)            # Aman
print(e._department)     # IT   (works, but you SHOULDN'T do this)
print(e.__salary)        # AttributeError: 'Employee' object has no attribute '__salary'

4. Name Mangling — How Private Really Works

When you write self.__salary, Python internally renames it to self._Employee__salary. This is called name mangling.

print(e._Employee__salary)     # 50000 - still reachable!
print([a for a in dir(e) if "salary" in a])   # ['_Employee__salary']

Python philosophy: "We are all consenting adults here." Python doesn't make data impossible to access — it makes it clear that you shouldn't. Name mangling mainly prevents accidental overriding of attributes in subclasses.

5. Private Methods

class PaymentGateway:
    def pay(self, amount):
        if self.__verify():
            print(f"Paid Rs {amount}")

    def __verify(self):          # private helper method
        print("Verifying user...")
        return True

p = PaymentGateway()
p.pay(500)
# Verifying user...
# Paid Rs 500
# p.__verify()   -> AttributeError

6. Getters and Setters — Controlled Access

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.__balance = balance

    def get_balance(self):                 # getter
        return self.__balance

    def deposit(self, amount):             # controlled setter
        if amount <= 0:
            print("Deposit amount must be positive!")
            return
        self.__balance += amount

    def withdraw(self, amount):
        if amount > self.__balance:
            print("Insufficient balance!")
        else:
            self.__balance -= amount

acc = BankAccount("Riya", 1000)
acc.deposit(-500)          # Deposit amount must be positive!
acc.withdraw(5000)         # Insufficient balance!
acc.deposit(500)
print(acc.get_balance())   # 1500

Now the balance can only change through methods that validate the data. That is encapsulation in action.

7. The Pythonic Way: @property

Getters and setters work, but obj.get_x() / obj.set_x(v) is Java style. In Python, use @property — it lets you use simple attribute syntax with validation behind the scenes.

class Student:
    def __init__(self, name, marks):
        self.name = name
        self.marks = marks            # this calls the setter below!

    @property
    def marks(self):                  # getter
        return self._marks

    @marks.setter
    def marks(self, value):           # setter with validation
        if not 0 <= value <= 100:
            raise ValueError("Marks must be between 0 and 100")
        self._marks = value

s = Student("Riya", 92)
print(s.marks)       # 92        (looks like a normal attribute)
s.marks = 95         # setter runs automatically
print(s.marks)       # 95
s.marks = 150        # ValueError: Marks must be between 0 and 100

Read-only property

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):                   # computed, read-only
        return round(3.14159 * self.radius ** 2, 2)

c = Circle(2)
print(c.area)        # 12.57
# c.area = 50        -> AttributeError (no setter defined)

8. Protected Members and Inheritance

class Vehicle:
    def __init__(self):
        self._speed = 0           # protected - meant for subclasses
        self.__engine_no = "E123" # private - mangled to _Vehicle__engine_no

class Car(Vehicle):
    def accelerate(self):
        self._speed += 20         # OK - subclasses may use protected members
        print("Speed:", self._speed)

    def engine(self):
        print(self.__engine_no)   # AttributeError! looks for _Car__engine_no

c = Car()
c.accelerate()                    # Speed: 20

9. Benefits of Encapsulation

  • Data protection — invalid values (negative balance, marks > 100) are rejected.
  • Flexibility — you can change internal implementation without breaking code that uses the class.
  • Maintainability — all rules for a piece of data live in one place.
  • Security of logic — helper methods stay internal.
  • Easier debugging — data changes only through known methods.

10. Mini Project: Secure User Account

class UserAccount:
    def __init__(self, username, password):
        self.username = username
        self.__password = None
        self.set_password(password)

    def set_password(self, new_password):
        if len(new_password) < 8:
            print("Password must be at least 8 characters!")
            return
        self.__password = new_password
        print("Password set successfully")

    def login(self, password):
        if password == self.__password:
            print(f"Welcome, {self.username}!")
        else:
            print("Wrong password")

user = UserAccount("riya_dev", "abc")        # Password must be at least 8 characters!
user.set_password("python@123")              # Password set successfully
user.login("hello")                          # Wrong password
user.login("python@123")                     # Welcome, riya_dev!

Real-world note: Real applications never store plain-text passwords — they store a hash (e.g. using hashlib or Django's password hashers). This example only demonstrates encapsulation.

Quiz: Predict the Output

Don't run the code yet! Write your answers in a notebook, then check the Answers section at the end of this blog.

Q1. What is the output?

class A:
    def __init__(self):
        self.__x = 10
a = A()
print(a.__x)

Q2. What is the output?

class A:
    def __init__(self):
        self.__x = 10
a = A()
print(a._A__x)

Q3. What is the output?

class B:
    def __init__(self):
        self._y = 5
print(B()._y)

Q4. How many underscores make an attribute private?

Q5. Is __init__ a private method?

Q6. What is the output?

class T:
    def __init__(self):
        self._c = 0
    @property
    def c(self):
        return self._c
t = T()
t.c = 5

Q7. Which decorator turns a method into a getter you can access like an attribute?

Practice Challenges

Solve these from easy to tricky:

  1. Create a class Product with a private price; add a getter and a setter that rejects negative prices.
  2. Rewrite challenge 1 using @property.
  3. Create a class Temperature where celsius cannot be set below -273.15, and fahrenheit is a read-only property.
  4. Create a Student class with a private list of marks and a method add_mark() that only accepts 0-100.
  5. Explain in your own words why Python uses name mangling instead of true private access.

Solutions for challenges 2 and 3 are in the Answers section.

Self-Check: Can You Do This?

Tick off each skill once you are confident:

Frequently Asked Interview Questions

Q: What is encapsulation in Python?
Bundling data and methods inside a class and restricting direct access to some of the data, allowing it to be changed only through controlled methods.

Q: Does Python support private variables?
Not strictly. A double underscore triggers name mangling (__x → _ClassName__x), which prevents accidental access but can still be bypassed.

Q: Difference between _x and __x?
_x is protected by convention only. __x is name-mangled, making it harder to access from outside and safe from accidental overriding in subclasses.

Q: Why use @property instead of get/set methods?
It keeps the simple attribute syntax (obj.x) while adding validation or computation, and you can add it later without changing the code that uses the class.

Q: Difference between encapsulation and abstraction?
Encapsulation hides data and controls access to it; abstraction hides implementation complexity and exposes only essential features.

Summary

  • Encapsulation = bundle data + methods and control access to the data.
  • Public: name, protected: _name (convention), private: __name (name mangling).
  • Private names become _ClassName__name internally.
  • Use getters/setters or, better, @property for validated access.
  • Benefits: data protection, flexibility, maintainability and cleaner code.

Quick Revision Cheat Sheet

ConceptSyntax / Key point
Publicself.name
Protectedself._name
Privateself.__name
Mangled nameobj._Class__name
Getter@property def x(self):
Setter@x.setter def x(self, v):

Answers

Quiz answers

  1. AttributeError – private attributes are name-mangled.
  2. 10 – accessed through the mangled name.
  3. 5 – protected is only a convention.
  4. Two leading underscores (__name), and not ending with two underscores.
  5. No – names with double underscores on both sides are special "dunder" methods, not mangled.
  6. AttributeError – the property has no setter, so it is read-only.
  7. @property

Challenge solutions

# Challenge 2
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price            # goes through the setter

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

    @price.setter
    def price(self, value):
        if value < 0:
            raise ValueError("Price cannot be negative")
        self.__price = value

p = Product("Pen", 10)
p.price = 15
print(p.price)                        # 15

# Challenge 3
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Temperature below absolute zero!")
        self._celsius = value

    @property
    def fahrenheit(self):
        return self._celsius * 9 / 5 + 32

t = Temperature(25)
print(t.fahrenheit)                   # 77.0

Conclusion

You can now protect your objects' data like a professional developer. Next, learn Polymorphism in Python — how the same method name can behave differently for different objects.

If this blog helped you, watch the full video above, subscribe to the channel and share it with a friend who is learning Python. Happy coding!