Chapter Ten Python Program/Notes

Chapter 10 - Advanced Attributes

  • There is no way to create a private attribute like in C++. Everything is accessible from everywhere.

  • You can use underscores and stuff to minimize name collisions. Call that name mangling.

  • HOWEVER, closures provide a way to create variables that are strictly inaccessible from the outside scope.

def create_counter():
# This variable is 'private' - it cannot be accessed directly
_count = 0

def increment():
    nonlocal _count
    _count += 1
    return _count

def get_count():
    return _count

# We only expose the interface, not the variable itself
return {
    "increment": increment,
    "get_count": get_count
}

counter = create_counter()

print(counter["increment"]()) # Output: 1
print(counter["increment"]()) # Output: 2
print(counter["get_count"]()) # Output: 2

# Attempting to access _count directly is impossible
# print(counter._count) -> AttributeError
  • By returning a dictionary or a custom object containing functions, you can simulate a class-like structure where the data is truly hidden.

  • You can also use closure to get a private-ish variable.

  • You can also make your variable a class, with a display function if you wanna make it that little bit harder to fuck with.

  • Getters get stuff (duh).

  • Property objects have methods to be used as decorators.

import os

# ==========================================
# 1. THE CLASS APPROACH (Pythonic "Privacy")
# ==========================================
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner            # Public
        self._internal_id = 12345     # "Protected" (Convention only)
        self.__balance = balance      # Name Mangled (Harder to access)

    # 4. PROPERTIES (Getters and Decorators)
    @property
    def balance(self):
        """The getter: Allows reading the private __balance."""
        print(f"Accessing balance for {self.owner}...")
        return f"${self.__balance:.2f}"

    @balance.setter
    def balance(self, value):
        """The setter: Adds validation logic before changing data."""
        if value < 0:
            print("Error: You can't have a negative balance!")
        else:
            self.__balance = value

# ==========================================
# 2. THE CLOSURE APPROACH (Strict Privacy)
# ==========================================
def create_secure_vault(initial_secret):
    """
    Demonstrates using a closure to make variables
    strictly inaccessible from the outside scope.
    """
    _secret_code = initial_secret  # Truly private variable

    def unlock(attempt):
        nonlocal _secret_code
        if attempt == _secret_code:
            return "Vault Opened!"
        return "Access Denied."

    def change_code(new_code):
        nonlocal _secret_code
        _secret_code = new_code
        return "Code Updated."

    # Exposing only the interface via a dictionary
    return {
        "unlock": unlock,
        "update": change_code
    }

# ==========================================
# DEMONSTRATION
# ==========================================

print("--- Part 1: Class Attributes & Properties ---")
acct = BankAccount("Alice", 1000)

# Accessing public and 'protected' (still works, just frowned upon)
print(f"Owner: {acct.owner}")
print(f"Internal ID: {acct._internal_id}")

# Property Access (The Getter)
print(f"Balance: {acct.balance}")

# Name Mangling: This will fail if called directly as acct.__balance
try:
    print(acct.__balance)
except AttributeError:
    print("Caught AttributeError: acct.__balance is not directly accessible!")

# But Python isn't strictly private; you can still find it if you're determined:
print(f"Mangled Access: {acct._BankAccount__balance}")


print("\n--- Part 2: Closures (True Hiding) ---")
vault = create_secure_vault("Blueberry123")

print(vault["unlock"]("WrongCode")) # Access Denied
print(vault["unlock"]("Blueberry123")) # Vault Opened!

# Note: There is literally no way to 'dot' into the vault to find _secret_code.
# vault._secret_code would throw an AttributeError because 'vault' is a dict.

Key Takeaways from the Implementation:

  • The “Honor System”: In the BankAccount class, the single underscore (_internal_id) is just a sign to other developers saying, “Hey, don’t touch this.” Python won’t actually stop you.

  • Name Mangling: When we used __balance, Python internally renamed it to _BankAccount__balance. It’s not private; it’s just wearing a disguise.

  • The Closure “Vault”: This is the “Chapter 10” secret sauce. Because _secret_code exists only in the local scope of create_secure_vault, it is physically impossible to access it from the global scope once the function finishes. You can only interact with it through the functions we “exposed” in the dictionary.

  • Property Decorators: The @property and @balance.setter allow you to treat a method like a simple variable, which is great for adding “display logic” (like adding a dollar sign) or validation without breaking the user’s ability to just type acct.balance.