Advertisement
🏗️ OOP

Python Encapsulation – Private Attributes and Property Decorators

Encapsulation is the principle of hiding an object's internal state and requiring all interaction to go through well-defined methods. In Python, this is achieved through naming conventions (underscores) and the @property decorator, which lets you control how attributes are accessed and modified.

⏱️ 20 min read🎯 Intermediate📅 Updated 2026

Python's Underscore Convention

Python uses naming conventions rather than strict access modifiers. Single underscore = "internal use". Double underscore = name-mangled (harder to access from outside).

Python
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner        # Public
        self._balance = balance   # Protected (convention only)
        self.__pin = "1234"       # Private (name-mangled)

acc = BankAccount("Alice", 1000)
print(acc.owner)      # Alice — OK
print(acc._balance)   # 1000 — works but discouraged
# print(acc.__pin)    # AttributeError!
print(acc._BankAccount__pin)  # "1234" — mangled name
▶ Output
Alice 1000 1234

@property – Clean Getters and Setters

@property lets you use attribute-style access while running code behind the scenes.

Python
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("Below absolute zero!")
        self._celsius = value

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

t = Temperature(25)
print(t.celsius)     # 25
print(t.fahrenheit)  # 77.0
t.celsius = 100
print(t.fahrenheit)  # 212.0
▶ Output
25 77.0 212.0

Using Properties for Validation

Properties enforce data integrity by validating values before setting them.

Python
class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade  # calls setter

    @property
    def grade(self):
        return self._grade

    @grade.setter
    def grade(self, value):
        if not 0 <= value <= 100:
            raise ValueError(f"Grade must be 0-100, got {value}")
        self._grade = value

s = Student("Alice", 95)
print(s.grade)   # 95
s.grade = 110    # Raises ValueError
▶ Output
95 ValueError: Grade must be 0-100, got 110
Advertisement

Read-Only Properties

Omit the setter to make a property read-only.

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

    @property
    def radius(self): return self._radius

    @property
    def area(self):   # Computed, read-only
        import math
        return math.pi * self._radius ** 2

c = Circle(5)
print(round(c.area, 2))  # 78.54
# c.area = 100           # AttributeError: can't set
▶ Output
78.54

Encapsulation in Python: Convention, Not Enforcement

Python has no truly private attributes. Instead it uses naming conventions plus a light "name mangling" trick. The philosophy: "we're all adults here" — signal intent, don't build walls.

NameMeansEnforced?
valuepublic
_value"internal, please don't touch"convention only
__valuename-mangled to _Class__valuediscourages, not prevents
class Account:
    def __init__(self):
        self._balance = 0          # protected by convention

    @property
    def balance(self):             # read-only public interface
        return self._balance

    @balance.setter
    def balance(self, amt):
        if amt < 0:
            raise ValueError("negative balance")
        self._balance = amt

a = Account()
a.balance = 100     # goes through the setter — validated
a.balance = -5      # ValueError

The @property pattern is the real tool: expose attributes directly, but route reads/writes through methods so you can validate or compute without changing the public API. Start with a plain attribute; promote it to a property only when you need logic. Double-underscore mangling is mainly for avoiding name clashes in subclasses, not security.

🏋️ Practical Exercise

Create a Temperature class:

  1. Store the value in a “private” attribute _celsius.
  2. Expose a celsius @property with a getter and setter.
  3. In the setter, reject temperatures below absolute zero (-273.15°C) with a ValueError.
  4. Add a read-only fahrenheit property computed from celsius.

🔥 Challenge Exercise

Build an Account class that protects its balance. Use a _balance attribute, a read-only balance property, and deposit() / withdraw() methods that validate amounts (no negative deposits, no overdrafts). Demonstrate that external code cannot set the balance directly through the property, only through the controlled methods.

📋 Summary

  • Encapsulation bundles data with the methods that operate on it and controls access to that data.
  • Python uses convention, not enforcement: a single underscore _x signals “internal use”.
  • A double underscore __x triggers name mangling to _ClassName__x, discouraging accidental access.
  • The @property decorator turns a method into a managed attribute with optional getter, setter, and deleter.
  • Setters are the place to validate values before they are stored.
  • Omitting a setter creates a read-only (computed) property.

Interview Questions on Encapsulation

  • What is encapsulation and why is it useful?
  • Does Python have truly private attributes?
  • What is the difference between a single underscore (_x) and double underscore (__x) prefix?
  • What is name mangling and when does it happen?
  • How does the @property decorator work?
  • How do you create a read-only attribute in Python?
  • Why use a property instead of a plain attribute?

FAQ

Can I make an attribute truly private in Python? +

No. Python deliberately has no access modifiers like private. The underscore conventions and name mangling discourage external access, but a determined caller can still reach the attribute. Python trusts the developer.

What exactly is name mangling? +

When you prefix an attribute with two underscores (e.g. __balance), Python rewrites it internally to _ClassName__balance. This avoids accidental clashes in subclasses and makes the attribute harder to access from outside.

Should I always use @property instead of plain attributes? +

No. Start with plain attributes. Switch to a property only when you need validation, computed values, or read-only access. Because the syntax is identical to attribute access, you can refactor later without breaking callers.

How do I make a property read-only? +

Define only the getter with @property and do not add a corresponding setter. Any attempt to assign to it raises an AttributeError.