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. .. code-block:: python 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. .. code-block:: python 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.