Ad – 728×90
🏗️ 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
Ad – 336×280

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