Logo OnlineCBT
PYTHON Tutorial
PYTHON · LESSON 34

Encapsulation, Properties and Validation

Protect valid object state through controlled attributes.

Level: Beginner to Advanced
Duration: ~60 Mins Deep-Dive
Updated: 26 Jul 2026

Lesson 34: Encapsulation, Properties and Validation

1. Introduction to Information Hiding and Data Integrity

In Lessons 32 and 33, we explored instantiating custom class blueprints and organizing behaviors into instance, class, and static methods. However, if external code can access and modify an object's internal variables directly (e.g., account.balance = -999999.0 or user.age = "INVALID"), the internal state becomes fragile, vulnerable to corruption, and completely unvalidated.

In enterprise software engineering, one of the foundational pillars of Object-Oriented Programming (OOP) is Encapsulation. Encapsulation combines internal state variables and the methods that operate on them into a single protected unit, while restricting direct external modification of internal fields.

In this lesson, we will master Python's naming conventions for Access Control (Public, Protected, and Private), explore the mechanics of Name Mangling, build Pythonic managed attributes using the `@property` decorator with Getters, Setters, and Deleters, enforce real-time input validation, and integrate encapsulated instances with Structural Pattern Matching (`match-case`).


2. Access Control Conventions in Python

Unlike languages such as Java or C++ that enforce strict access control at the compiler level using explicit keywords (`public`, `protected`, `private`), Python operates under the philosophy: "We are all consenting adults here."

Python relies on explicit naming conventions to signal the intended visibility and privacy scope of attributes and methods:

  • Public Attributes
  • self.attribute_name
  • Fully accessible and mutable from anywhere inside or outside the class. Default behavior.
  • Protected Attributes
  • self._attribute_name
    (Single leading underscore)
  • Non-public indicator: Signals to developers that the attribute is internal to the class and its subclasses. Direct external access is discouraged but technically allowed.
  • Private Attributes
  • self.__attribute_name
    (Double leading underscore)
  • Strictly Private: Triggers Python's internal **Name Mangling** mechanism to prevent accidental name collisions in subclasses and block direct external access.
Access Level Naming Convention Syntax Internal Scope Visibility & Policy Expectation

3. The Mechanics of Name Mangling

When an attribute name begins with at least two leading underscores (and at most one trailing underscore, e.g., __balance), Python automatically transforms its attribute name inside the object's instance dictionary (__dict__).

The identifier __balance defined inside class BankAccount is mangled dynamically to: _BankAccount__balance.

Why Name Mangling Exists: Name mangling is NOT designed as a cryptographic security measure. Its primary architectural purpose is to prevent attribute name collisions when parent classes are extended by subclasses in deep inheritance hierarchies!
NAME_MANGLING_DEMO.PY
class SecureVault:
    def __init__(self, owner: str, secret_key: str):
        self.owner = owner               # Public attribute
        self._access_level = "LEVEL_2"   # Protected attribute
        self.__secret_key = secret_key   # Private attribute (Name Mangled)

# Instantiating object
vault = SecureVault("Alice", "ALPHA_9091")

print("=== Accessing Public & Protected Attributes ===")
print("Public Owner    :", vault.owner)
print("Protected Level :", vault._access_level)

print("\n=== Demonstrating Name Mangling ===")
# Attempting direct access to private attribute triggers AttributeError
try:
    print(vault.__secret_key)
except AttributeError as err:
    print("Caught Direct Access Error:", err)

# Accessing the mangled attribute directly via instance dictionary
mangled_name = "_SecureVault__secret_key"
print(f"Mangled Access ({mangled_name}):", getattr(vault, mangled_name))
print("Instance __dict__ Keys:", list(vault.__dict__.keys()))
OUTPUT
=== Accessing Public & Protected Attributes ===
Public Owner    : Alice
Protected Level : LEVEL_2

=== Demonstrating Name Mangling ===
Caught Direct Access Error: 'SecureVault' object has no attribute '__secret_key'
Mangled Access (_SecureVault__secret_key): ALPHA_9091
Instance __dict__ Keys: ['owner', '_access_level', '_SecureVault__secret_key']

4. Pythonic Managed Attributes: The `@property` Decorator

In legacy languages, protecting a private attribute requires writing explicit getter and setter methods (e.g., obj.get_balance() and obj.set_balance(100)). This pattern breaks clean attribute dot-notation syntax (obj.balance) and forces callers to rewrite code if a public attribute later requires validation.

Python solves this elegantly using the `@property` decorator. Properties allow wrapping internal attributes behind methods while exposing them to callers with standard attribute dot notation (obj.attribute)!

1. Anatomy of a Property Block

  • @property: Declares the **Getter method**. Executed when reading obj.attr.
  • @attribute.setter: Declares the **Setter method**. Executed when assigning obj.attr = value. Enforces validation rules!
  • @attribute.deleter: Declares the **Deleter method**. Executed when calling del obj.attr.
PROPERTY_DECORATOR_DEMO.PY
class EmployeeSalary:
    def __init__(self, emp_name: str, initial_salary: float):
        self.emp_name = emp_name
        # Invokes property setter validation directly during construction!
        self.salary = initial_salary

    # 1. GETTER PROPERTY
    @property
    def salary(self) -> float:
        """Getter: Controls read access to the underlying protected variable _salary."""
        return self._salary

    # 2. SETTER PROPERTY WITH VALIDATION
    @salary.setter
    def salary(self, value: float):
        """Setter: Enforces real-time validation rules during assignment."""
        if not isinstance(value, (int, float)):
            raise TypeError(f"Salary must be a numerical float! Got: {type(value).__name__}")
        if value < 30000.0:
            raise ValueError(f"Salary ${value:,.2f} is below standard minimum wage ($30,000.00)!")
        
        # Store validated value in protected instance variable
        self._salary = float(value)

# Testing Managed Attributes
emp = EmployeeSalary("Alice Dev", 85000.0)

print("=== Reading Managed Property ===")
print(f"Employee: {emp.emp_name} | Current Salary: ${emp.salary:,.2f}")

print("\n=== Updating Property cleanly via Dot Notation ===")
emp.salary = 95000.0  # Calls @salary.setter transparently!
print(f"Updated Salary: ${emp.salary:,.2f}")

print("\n=== Attempting Invalid Property Mutation ===")
try:
    emp.salary = 15000.0  # Below minimum wage threshold!
except ValueError as val_err:
    print(f"VALIDATION REJECTED -> {val_err}")
OUTPUT
=== Reading Managed Property ===
Employee: Alice Dev | Current Salary: $85,000.00

=== Updating Property cleanly via Dot Notation ===
Updated Salary: $95,000.00

=== Attempting Invalid Property Mutation ===
VALIDATION REJECTED -> Salary $15,000.00 is below standard minimum wage ($30,000.00)!

5. Advanced Validation and Computed Properties

Properties are not restricted to protecting state variables; they can also compute values dynamically on the fly based on internal object state without storing redundant data in memory.

COMPUTED_PROPERTIES_DEMO.PY
class Rectangle:
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

    @property
    def width(self) -> float:
        return self._width

    @width.setter
    def width(self, value: float):
        if value <= 0:
            raise ValueError("Width must be positive!")
        self._width = float(value)

    @property
    def height(self) -> float:
        return self._height

    @height.setter
    def height(self, value: float):
        if value <= 0:
            raise ValueError("Height must be positive!")
        self._height = float(value)

    # COMPUTED PROPERTY (Read-Only: No setter defined)
    @property
    def area(self) -> float:
        """Calculates surface area dynamically on demand."""
        return self._width * self._height

rect = Rectangle(10.0, 5.0)
print(f"Dimensions: {rect.width}x{rect.height} -> Computed Area: {rect.area}")

# Updating width automatically recalculates area!
rect.width = 20.0
print(f"Updated Width: {rect.width} -> Re-computed Area: {rect.area}")

# Attempting to assign to a read-only computed property triggers AttributeError
try:
    rect.area = 500.0
except AttributeError as err:
    print("Caught Read-Only Error:", err)
OUTPUT
Dimensions: 10.0x5.0 -> Computed Area: 50.0
Updated Width: 20.0 -> Re-computed Area: 100.0
Caught Read-Only Error: property 'area' of 'Rectangle' object has no setter

6. Integrating Encapsulated Objects with `match-case` Pattern Matching

Python 3.10+ Class Pattern Matching integrates cleanly with managed properties and encapsulated attributes. Structural patterns inspect property getter returns dynamically during match evaluations.

MATCH_CASE_ENCAPSULATION_DISPATCHER.PY
class AccountProfile:
    def __init__(self, account_id: str, credit_score: int, is_flagged: bool = False):
        self.account_id = account_id
        self.credit_score = credit_score
        self.is_flagged = is_flagged

    @property
    def credit_score((self) -> int:
        return self._credit_score

    @credit_score.setter
    def credit_score(self, value: int):
        if not (300 <= value <= 850):
            raise ValueError(f"Credit score must be between 300 and 850! Got: {value}")
        self._credit_score = value

def evaluate_loan_application(account_obj: AccountProfile):
    """
    Evaluates encapsulated objects using match-case pattern matching.
    Demonstrates evaluating property attributes safely.
    """
    match account_obj:
        case AccountProfile(is_flagged=True, account_id=aid):
            return f"LOAN REJECTED: Account #{aid} is flagged for compliance security audit!"
            
        case AccountProfile(credit_score=score) if score >= 750:
            return f"LOAN APPROVED (PRIME TIER): Credit Score {score} qualifies for lowest interest rate."
            
        case AccountProfile(credit_score=score) if score >= 650:
            return f"LOAN APPROVED (STANDARD TIER): Credit Score {score} qualifies for standard interest rate."
            
        case AccountProfile(credit_score=score):
            return f"LOAN REJECTED: Credit Score {score} is below minimum requirement (650)."
            
        case _:
            return "ERROR: Provided object is not a valid AccountProfile."

# Testing the pattern dispatcher
p1 = AccountProfile("ACC_801", 780, is_flagged=False)
p2 = AccountProfile("ACC_802", 680, is_flagged=False)
p3 = AccountProfile("ACC_803", 790, is_flagged=True)  # Flagged!

print(evaluate_loan_application(p1))
print(evaluate_loan_application(p2))
print(evaluate_loan_application(p3))
OUTPUT
LOAN APPROVED (PRIME TIER): Credit Score 780 qualifies for lowest interest rate.
LOAN APPROVED (STANDARD TIER): Credit Score 680 qualifies for standard interest rate.
LOAN REJECTED: Account #ACC_803 is flagged for compliance security audit!

7. Frequently Asked Interview Questions with Answers

Q1: What is Encapsulation in Object-Oriented Programming, and how is it implemented in Python?
Answer: Encapsulation is the practice of bundling data state and related operations inside a protected class structure while restricting direct external access to internal fields. In Python, encapsulation is implemented using naming conventions for non-public attributes (single leading underscore _attr and private double leading underscore __attr) alongside managed properties using the @property decorator.
Q2: What is Name Mangling in Python, and when is it triggered?
Answer: Name mangling is triggered when an attribute identifier starts with two leading underscores (e.g., __variable). Python renames the attribute internally to _ClassName__variable in memory. This prevents accidental attribute overriding and namespace collisions in inherited subclasses.
Q3: What is the advantage of using `@property` over traditional Getter and Setter methods?
Answer: @property preserves clean, Pythonic attribute dot-notation syntax (obj.val = 10) while enabling real-time validation logic under the hood. It allows refactoring public attributes into validated fields without breaking existing external call interfaces.
Q4: How do you create a Read-Only Property in Python?
Answer: Define a method decorated with @property without providing a corresponding @attribute.setter method. Any attempt to assign a new value to the property externally will raise an AttributeError.
Q5: How does the Setter property handle constructor initialization validation?
Answer: By assigning the constructor parameter directly to the managed property name inside __init__ (e.g., self.age = initial_age rather than self._age = initial_age), instantiation immediately routes through the @age.setter validation method!

8. Homework & Practical Assignments

Task 1: Bank Account Class with Validated Properties

Create a script named validated_bank_task.py inside your lesson_34 folder:

  • Define class BankAccount(account_number: str, owner: str, initial_balance: float).
  • Protect balance using _balance protected attribute and expose it via @property getter.
  • Add a @balance.setter that raises ValueError if balance is assigned a negative number.
  • Add methods deposit(amount) and withdraw(amount) utilizing property setters.
  • Instantiate an account, test valid and invalid balance mutations, and catch exceptions cleanly using try-except.

Task 2: Pattern-Matched Credit Card Validator

Create a script named card_validator_task.py:

  • Define class CreditCardRecord(card_number: str, credit_limit: float).
  • In the card_number setter, validate that string length is exactly 16 digits using .isdigit().
  • Write function audit_card_status(card_obj) using match-case pattern matching:
    • case CreditCardRecord(credit_limit=lim) if lim >= 10000.0 → Return "PREMIUM_CREDIT_LINE".
    • case CreditCardRecord(credit_limit=lim) if lim >= 2000.0 → Return "STANDARD_CREDIT_LINE".
    • case _ → Return "RESTRICTED_CREDIT_LINE".
  • Test function with 3 card instances and print results using f-strings.

Task 3: Master Review Capstone Project — Enterprise Secure Data Store OS (`secure_datastore_os.py`)

Create a script named secure_datastore_os.py inside your lesson_34 folder. This assignment tests and integrates **ALL concepts learned across Lessons 1 through 34**.

Project Architectural Requirements Specification:

  1. Encapsulation, Properties & Validation (Lesson 34):
    • Define core domain entity class: SecureRecord(record_id, label, raw_value, classification).
    • Use protected attributes (_raw_value, _classification) and private fields (__access_key).
    • Expose properties with getters and setters enforcing validation (e.g., classification must be in {"CONFIDENTIAL", "RESTRICTED", "PUBLIC"}).
  2. Advanced OOP Methods & Constructors (Lessons 32-33):
    • Implement class method factory from_json_string(cls, json_str) to construct instances.
    • Implement static method validate_record_id(rid) to check format standards.
    • Track total registered records using a shared class attribute counter.
  3. Observability & Diagnostics (Lesson 31):
    • Configure a logging FileHandler writing event messages to datastore.log.
    • Add internal developer assertions (assert) verifying invariant states.
  4. Context Managers & Fault Tolerance (Lessons 29-30):
    • Build a class-based context manager VaultSession(db_path) managing persistent storage file locks.
    • Define custom exception classes: DataStoreError(Exception), SecurityAccessError(DataStoreError). Wrap executions in 4-block try-except-else-finally suites.
  5. CSV & JSON Persistence Layer (Lessons 27-28):
    • Maintain storage directory vault_os_db/ using pathlib.Path.
    • Persist record objects to records.json and export access history logs to access_audit.csv.
  6. Nested Collections, Comprehensions & Data Structures (Lessons 21-26):
    • Maintain an in-memory database mapping record IDs to Class Instances.
    • Use List and Dictionary Comprehensions to filter and map active records dynamically.
  7. Advanced Function Parameters & Scope (Lessons 15-17):
    • Structure logic into pure modular functions with type hints, docstrings, early return Guard Clauses, and *args / **kwargs logging.
  8. Indefinite & Definite Loops (Lessons 13-14):
    • Run the interactive CLI interface inside a continuous while True menu loop.
    • Iterate through output reports using for loops with enumerate() and .items().
  9. Pattern Matching CLI Dispatcher (Lesson 12):
    • Process user CLI command tokens inside a match-case block:
      • case ["STORE", rid, label, val_str, class_level] → Instantiate SecureRecord, trigger property validations, and persist.
      • case ["FETCH", rid] → Fetch instance and display formatted property values.
      • case ["UPDATE", rid, "VAL", new_val] → Mutate property value via setter.
      • case ["AUDIT", "CLASS"] → Inspect object instances using Class Pattern Matching.
      • case ["EXIT" | "QUIT"] → Terminate session safely using a sentinel flag.
      • case _ → Output command error message.
  10. Conditionals, Formatting & Foundations (Lessons 1-11):
    • Sanitize all inputs using .strip() and .upper().
    • Format tabular reports using f-strings with field width alignment specifiers (:<15, :>10) and clear visual borders.

9. File & Workspace Directory Structure

Standard Course Workspace Layout:

Ensure all exercise files are stored inside their corresponding lesson directories:

python_mastery_course/
│
├── lesson_01/ ... lesson_33/
│
└── lesson_34/
    ├── name_mangling_demo.py
    ├── property_decorator_demo.py
    ├── computed_properties_demo.py
    ├── match_case_encapsulation_dispatcher.py
    ├── validated_bank_task.py       <-- (Task 1)
    ├── card_validator_task.py       <-- (Task 2)
    └── secure_datastore_os.py       <-- (Task 3: Master Review Capstone)
        

10. What We Will Learn Next

Next Up: Lesson 35 — Inheritance and Polymorphism

Now that you master Encapsulation, protected attributes, and managed properties using `@property`, we will explore extending class hierarchies with Inheritance and Polymorphism!

In the next lesson, we will cover:

  • Understanding Object-Oriented Inheritance (Parent vs Child classes).
  • Method Overriding and accessing parent behavior using the super() function.
  • Understanding Polymorphism and Duck Typing in Python.
  • Multiple Inheritance mechanics and the Method Resolution Order (MRO).
  • Combining inherited class hierarchies with match-case pattern dispatchers.

📝 Live Lesson Practice

HTML/CSS JavaScript Python C++ C PHP
⌨️ Practice Inputs (लाइव इनपुट भरें) (खाली होने पर RED, भरने पर GREEN underline)
💻 Code Editor (Monaco VS Code Engine)
👀 Live Preview
Address Contact

+91 7877547686

E-mail

onlinecbtportal@gmail.com

Helpline Number

+91 7877547686


Click To Download
Get it on Google Play

ऐप डाउनलोड करने
के लिए Google Play पर
उपलब्ध है

WhatsApp Chat