-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
128: Encapsulation Conventions: _protected and __private
A few years ago, I was lead on a project where we had a junior dev working on a complex data-parsing library. He noticed a method in one of my classes called _calculate_internal_checksum(). Because it worked exactly how he needed it to, he called it directly from his own module. For three months, everything was fine. Then, I refactored that method, renamed it, and changed its return type because it was an internal implementation detail that didn't affect the public API. The moment we deployed, the entire ingestion pipeline crashed. He had tied his code to a "private" piece of my class that I thought was safe to change.
In languages like Java or C#, you have keywords like private and protected that the compiler enforces. If you try to touch a private member from outside the class, the code simply won't compile. Python is different. Python operates on the "consenting adults" philosophy: it assumes you know what you're doing and won't stop you from breaking things, but it gives you conventions to signal your intent.
The "Gentleman's Agreement" of the Single Underscore
When you prefix a variable or method with a single underscore, like _status or _update_cache(), you aren't actually locking the door. You're just putting up a sign that says "Staff Only."
Technically, Python does nothing to stop someone from accessing obj._status. However, the community recognizes this as a "protected" member. I use this whenever I have a method that is essential for the class to function but would be confusing or dangerous for an end-user to call directly. It tells other developers: "I might change this method's name or logic tomorrow, so if you rely on it, your code might break."
class SmartLight:
def __init__(self, brightness):
self.brightness = brightness
self._connection_timeout = 30 # Internal setting, don't touch!
def _check_connection(self):
print("Checking hardware connection...")
return True
def set_brightness(self, level):
if self._check_connection():
self.brightness = level
Name Mangling with Double Underscores
Sometimes, a single underscore isn't enough, especially when you're building a class hierarchy. If you use a double underscore, like __secret, Python triggers a mechanism called name mangling. This is less about security and more about preventing "name clashes" in subclasses.
When Python sees __private_var inside a class called Account, it internally renames it to _Account__private_var. This makes it much harder (though still not impossible) to access from outside the class or to accidentally override it in a child class.
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Mangled to _BankAccount__balance
def get_balance(self):
return self.__balance
class SavingsAccount(BankAccount):
def __init__(self, balance, interest_rate):
super().__init__(balance)
self.__balance = 0 # This creates a NEW variable, it doesn't overwrite the parent's!
account = BankAccount(1000)
# print(account.__balance) # This would raise an AttributeError
print(account._BankAccount__balance) # This works, but it's a huge "no-no" in professional code.
I generally advise against using double underscores unless you are writing a library that will be inherited by dozens of other developers. For 95% of your work, the single underscore is the right tool. It communicates intent without adding the weird overhead of name mangling.
📋 Practical Task
Exercise: Implementing a Secure CreditCard Validator
You need to build a CreditCard class that protects sensitive data while allowing controlled access. Follow these requirements:
- Create a class
CreditCardthat takescard_numberandcvvin its constructor. - Store the
card_numberusing name mangling (double underscore) so it isn't easily accessible from outside the class. - Store the
cvvusing the protected convention (single underscore). - Implement a method
get_masked_number()that returns the card number but replaces all but the last four digits with asterisks (e.g.,************1234). - Implement a method
validate_cvv(input_cvv)that returnsTrueif the input matches the protected CVV, andFalseotherwise.
Test your class by attempting to access __card_number directly from an instance to confirm it raises an AttributeError, then use your masking method to verify it works.
There are no comments for now.