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.
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()))
=== 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 readingobj.attr.@attribute.setter: Declares the **Setter method**. Executed when assigningobj.attr = value. Enforces validation rules!@attribute.deleter: Declares the **Deleter method**. Executed when callingdel obj.attr.
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}")
=== 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.
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)
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.
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))
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
_attr and private double leading underscore __attr) alongside managed properties using the @property decorator.
__variable). Python renames the attribute internally to _ClassName__variable in memory. This prevents accidental attribute overriding and namespace collisions in inherited subclasses.
@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.
@property without providing a corresponding @attribute.setter method. Any attempt to assign a new value to the property externally will raise an AttributeError.
__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
_balanceprotected attribute and expose it via@propertygetter. - Add a
@balance.setterthat raisesValueErrorif balance is assigned a negative number. - Add methods
deposit(amount)andwithdraw(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_numbersetter, validate that string length is exactly 16 digits using.isdigit(). - Write function
audit_card_status(card_obj)usingmatch-casepattern 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:
- 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"}).
- Define core domain entity class:
- 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.
- Implement class method factory
- Observability & Diagnostics (Lesson 31):
- Configure a
loggingFileHandler writing event messages todatastore.log. - Add internal developer assertions (
assert) verifying invariant states.
- Configure a
- 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-blocktry-except-else-finallysuites.
- Build a class-based context manager
- CSV & JSON Persistence Layer (Lessons 27-28):
- Maintain storage directory
vault_os_db/usingpathlib.Path. - Persist record objects to
records.jsonand export access history logs toaccess_audit.csv.
- Maintain storage directory
- 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.
- Advanced Function Parameters & Scope (Lessons 15-17):
- Structure logic into pure modular functions with type hints, docstrings, early return Guard Clauses, and
*args/**kwargslogging.
- Structure logic into pure modular functions with type hints, docstrings, early return Guard Clauses, and
- Indefinite & Definite Loops (Lessons 13-14):
- Run the interactive CLI interface inside a continuous
while Truemenu loop. - Iterate through output reports using
forloops withenumerate()and.items().
- Run the interactive CLI interface inside a continuous
- Pattern Matching CLI Dispatcher (Lesson 12):
- Process user CLI command tokens inside a
match-caseblock:case ["STORE", rid, label, val_str, class_level]→ InstantiateSecureRecord, 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.
- Process user CLI command tokens inside a
- 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.
- Sanitize all inputs using
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)
OnlineCBT