Skip to contentSkip to navigation
MODULE 09 · LESSON 05

Encapsulation & @property Decorators

Protect internal class state with private conventions, validated getters, and setter mutation guards.

Core Learning Milestones:
Convention, Not Enforcement
_attr means 'internal, please'
Name Mangling
__attr -> _ClassName__attr
@property
Attribute syntax, method behavior
Backing Variables
Avoid infinite recursion
Estimated Time
30 Minutes
Comprehensive deep dive
Difficulty Level
Intermediate
Builds on Inheritance
Knowledge Check
15 Questions
+35 XP Potential
01 / Foundations

Python Trusts You — With Conventions, Not Locks

Unlike languages with compiler-enforced private keywords, Python relies on naming conventions: a single leading underscore (_attr) signals "internal, please don't touch," but nothing stops external code from touching it anyway.

A double leading underscore (__attr) is different — it triggers real name mangling, rewriting the name to _ClassName__attr internally. This exists to prevent accidental collisions in subclasses, not to provide security.

Treating __attr as real security
self.__password = pw
# still reachable via _Class__password
Using @property for real validation
@celsius.setter
def celsius(self, v):
  if v < -273.15: raise ValueError(...)
Think of it like this:

An underscore prefix is like a "staff only" sign on an office door — it's a polite request, not a locked door. @property is like a reception desk: visitors interact with it using simple, friendly syntax, while the actual validation and computation happen behind the counter.

Full Contract in Action

Inspecting Single vs Double Underscore Attributes

Run the snippet below to see exactly how double-underscore mangling changes an attribute's real name:

visibility.py — Press 'Run Code' to execute in your browser
class SecureVault:
    def __init__(self, key, secret_data):
        self.key = key              # Public
        self._audit_tag = "V1"       # Protected by convention
        self.__secret = secret_data  # Name-mangled

vault = SecureVault("API_KEY", "TOP_SECRET_PAYLOAD")

print("Public:", vault.key)
print("Protected:", vault._audit_tag)

# Direct access to __secret raises AttributeError:
try:
    print(vault.__secret)
except AttributeError as err:
    print("Direct access caught:", err)

# But the mangled attribute is readily accessible in __dict__:
print("Mangled attribute accessed:", vault._SecureVault__secret)
Encapsulation Glossary: Core Concepts
_attrConvention

A community signal meaning "internal implementation detail" — Python does not enforce it.

__attrMangled

Rewritten to _ClassName__attr to avoid accidental subclass attribute collisions.

@propertyDescriptor

Lets attribute-style reads/writes actually run getter/setter methods underneath.

cached_propertyMemoize

Computes an expensive read-only property once, then reuses the cached result.

02 / Visibility Level Explorer

Compare Every Visibility Level

Click through public, protected, mangled, and property-based attribute access:

Public: self.value
self.value = 10 vault.value # freely accessible

No leading underscore means 'use this freely' — the default for anything meant to be part of the public interface.

03 / @property Pattern Reference

Backing Variables, Validation, and Caching

These three patterns cover nearly every real-world use of @property.

Backing variable pattern

self._val = val # inside setter

Always store property state in a differently-named backing attribute to avoid infinite recursion.

self.val = val # inside __init__

Assigning through the property in __init__ ensures constructor inputs get the same validation as later writes.

Getter / setter validation

@celsius.setter def celsius(self, value): ...

The setter runs on every assignment, letting you reject invalid values before they're stored.

Cached computation

@functools.cached_property

Memoizes an expensive read-only computed property on immutable objects, computing it only once.

04 / Hands-on Examples

Worked Hands-on Walkthroughs

Progress step-by-step from underscore conventions through validated properties to read-only computed access:

Walkthrough 01 · FoundationLevel 1 · Foundation

Inspecting Single vs Double Underscore Attributes

Visibility
Problem Scenario

Name mangling renames __secret to _SecureVault__secret to avoid collision, not to enforce cryptographic secrecy.

Executable Solution
Inspecting Single vs Double Underscore Attributes
class SecureVault:
    def __init__(self, key, secret_data):
        self.key = key              # Public
        self._audit_tag = "V1"       # Protected by convention
        self.__secret = secret_data  # Name-mangled

vault = SecureVault("API_KEY", "TOP_SECRET_PAYLOAD")

print("Public:", vault.key)
print("Protected:", vault._audit_tag)

# Direct access to __secret raises AttributeError:
try:
    print(vault.__secret)
except AttributeError as err:
    print("Direct access caught:", err)

# But the mangled attribute is readily accessible in __dict__:
print("Mangled attribute accessed:", vault._SecureVault__secret)
Step-by-Step Explanation

Name mangling renames __secret to _SecureVault__secret to avoid collision, not to enforce cryptographic secrecy.

Walkthrough 02 · Building UpLevel 2 · Applied

Comprehensive Property Lifecycle with Validation

Validation
Problem Scenario

Assigning to sensor.celsius in __init__ and later assignments both execute the setter validation.

Executable Solution
Comprehensive Property Lifecycle with Validation
class TemperatureSensor:
    def __init__(self, celsius=0.0):
        # Channel through property setter for initial validation!
        self.celsius = celsius

    @property
    def celsius(self):
        """Read the temperature in Celsius."""
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        """Validate physical lower bound (Absolute Zero)."""
        if not isinstance(value, (int, float)):
            raise TypeError("Temperature must be a numerical value")
        if value < -273.15:
            raise ValueError("Temperature cannot drop below Absolute Zero (-273.15°C)")
        self._celsius = float(value)

    @celsius.deleter
    def celsius(self):
        raise PermissionError("Cannot delete primary sensor reading")

sensor = TemperatureSensor(25.0)
print(f"Initial temp: {sensor.celsius}°C")
sensor.celsius = 100.0
print(f"Boiling temp: {sensor.celsius}°C")

try:
    sensor.celsius = -300.0
except ValueError as err:
    print("Validation caught:", err)
Step-by-Step Explanation

Assigning to sensor.celsius in __init__ and later assignments both execute the setter validation.

Walkthrough 03 · MasteryLevel 3 · Architecture

Computed Read-Only Properties vs Cached Properties

Read-Only
Problem Scenario

The checksum computation ran only once; the second read retrieved the cached value immediately.

Executable Solution
Computed Read-Only Properties vs Cached Properties
import time
from functools import cached_property

class DatasetAnalyzer:
    def __init__(self, data_points):
        self.data_points = list(data_points)

    # 1. Computed property: recalculates whenever accessed
    @property
    def count(self):
        return len(self.data_points)

    # 2. Cached property: expensive work performed once per instance
    @cached_property
    def checksum(self):
        print("--- Computing expensive SHA256 checksum ---")
        time.sleep(0.01)  # Simulating heavy cryptographic work
        return sum(self.data_points) % 10007

analyzer = DatasetAnalyzer([10, 20, 30, 40, 50])
print("Count:", analyzer.count)

# First access calculates
print("First checksum read:", analyzer.checksum)
# Second access reads directly from cache (__dict__)
print("Second checksum read (cached):", analyzer.checksum)
Step-by-Step Explanation

The checksum computation ran only once; the second read retrieved the cached value immediately.

05 / Traps to Avoid

Common Traps & How to Avoid Them

Study the mistakes and clean fixes below before writing your own properties:

!Trap 1: Syntax or Name Typos in Encapsulation & @property
Common Trap
The Bug (Avoid)Buggy Code
# Mistyped variable or missing closing parenthesis result = encapsulation____property_val
What Happens:
NameError: name is not defined
The Clean FixCorrected Code
# Declare and verify variables before execution val = 10 print("Defined correctly:", val)
Recommended Practice:
Always store property state in a differently-named backing attribute to avoid infinite recursion.
Why It Happens

Beginners frequently encounter typos or forgotten definitions before using an identifier.

!Trap 2: Unintended State Mutation or Scope Leakage
Subtle Trap
The Bug (Avoid)Buggy Code
# Modifying shared state or unexpected variable shadowing def update_state(data=[]): data.append(1) return data
What Happens:
Default mutable argument accumulates values across separate function calls.
The Clean FixCorrected Code
def update_state(data=None): if data is None: data = [] data.append(1) return data
Recommended Practice:
Always store property state in a differently-named backing attribute to avoid infinite recursion.
Why It Happens

Default arguments in Python are evaluated once at function definition time, not at invocation time.

!Trap 3: Resource Leaks & High-Scale Performance Regressions
Architecture Trap
The Bug (Avoid)Buggy Code
# Opening resources in a tight loop without closing or bounding memory files = [open(f"log_{i}.txt", "w") for i in range(5000)]
What Happens:
OSError: [Errno 24] Too many open files (file descriptor exhaustion)
The Clean FixCorrected Code
for i in range(5): with open(f"log_{i}.txt", "w") as f: f.write("Safe")
Recommended Practice:
Always store property state in a differently-named backing attribute to avoid infinite recursion.
Why It Happens

Failing to release system resources leads to process termination under heavy production workloads.

06 / Real-World Systems

Real-World Systems in Action

See how scripts, cloud microservices, and distributed systems rely on encapsulated, validated state:

Developer ProductivityFoundation Script

Local Developer Automation Tool

SYSTEM #1
Organization Context

Individual developers and IT administrators automate everyday repetitive workflows.

Why This Pattern Works Here

A clean standalone script providing fast, local automation with zero dependencies.

Production Code Wrapper
Local Developer Automation Tool
class BankAccount:
    def __init__(self, balance):
        self._balance = balance
    @property
    def balance(self):
        return self._balance

acc = BankAccount(1000)
print("Balance:", acc.balance)
Cloud MicroservicesScaled Service · Applied

Backend API Service Integration

SYSTEM #2
Organization Context

SaaS platforms run backend microservices processing requests concurrently.

Why This Pattern Works Here

A modular service handler integrating validation and structured responses.

Production Code Wrapper
Backend API Service Integration
# Service integration layer
def handle_request(payload: dict) -> dict:
    return {"status": "success", "processed": True}
Distributed SystemsEnterprise · Architecture

High-Throughput Cloud Architecture

SYSTEM #3
Organization Context

Domain-Driven Design (DDD) prevents invalid state corruption across microservice entities using encapsulated property setters.

Why This Pattern Works Here

Domain-Driven Design (DDD) prevents invalid state corruption across microservice entities using encapsulated property setters.

Production Code Wrapper
High-Throughput Cloud Architecture
# High-resilience production pattern
import time

def process_stream(events: list) -> int:
    processed = 0
    for event in events:
        if event:
            processed += 1
    return processed
Key Takeaways

Key Points & Concept Review

  • Python relies on community visibility conventions (_attr) rather than hard compiler private access modifiers.
  • Double-underscore attributes (__attr) trigger name mangling to prevent accidental subclass override collisions, not for security.
  • Use @property to expose simple attribute syntax while executing dynamic computation or validation under the hood.
  • Always store property state in a backing variable (e.g. self._val) to prevent infinite recursion.
  • Assign to properties in __init__ (self.val = val) to ensure constructor inputs undergo identical validation checks.
  • Leverage functools.cached_property to memoize expensive, read-only property computations on immutable objects.
Study Guide & References

Frequently Asked Questions & Reference Hub

FAQ & References
Growth Mindset: Properties Click Once You Hit the Recursion Bug

Forgetting the backing variable and triggering infinite recursion in a property setter is a completely normal rite of passage — once you've debugged it once, the self._val pattern never leaves your muscle memory.

Related Built-in References Right Here
PyLabs Library Docs
Ready for Action

You've Mastered Encapsulation & @property!

You understand visibility conventions, name mangling, and validated computed properties. Now lock in your knowledge with hands-on practice.

Hands-on Code Challenge

Write a validated @property with a proper backing variable.

+40 XP Reward

Earn experience points toward your developer rank as soon as you pass.

~3 Minutes to Complete

Bite-sized knowledge check with instant explanations for every question.

Worked example

Validate attribute assignment transparently with @property.

Example
class Box:
    def __init__(self, s): self._s = s
    @property
    def s(self): return self._s
print(Box(10).s)

How it works, step by step

  1. @property defines a getter accessed like an attribute.

Your notes

Sign in to keep private notes alongside this lesson.

Sign in to take notes

Encapsulation & @property Decorators practice

Sign-in required

Create a class User with an __init__(self, username, age) that stores self.username = username and uses a @property for age. The setter should raise ValueError("Age cannot be negative") if new_age < 0, otherwise store in self._age. Create u = User("Sam", 25), update u.age = 26, and print: User Sam is 26 years old.

Coding Practice is Locked

Sign in to access the interactive Python editor, write and run code checks against automated tests, use hints, and earn XP.

Practice quiz

Check yourself

15 questions with explanations after submission, so you can correct course as you go.

Encapsulation & @property Decorators quiz

Locked

You must be signed in to take this quiz. Sign in to test your understanding, track your answers, and earn XP.

Lesson incomplete

Not completed

Create an ID or sign in to save lesson completion across devices.

Create ID to save
Module lesson progress0/6 complete
Module cheat sheet