Encapsulation & Properties
Encapsulation is the principle of bundling data and the methods that work on that data into a single unit (a class) and restricting access to the internal state.
In languages like Java, this is done with private keywords. Python follows a different philosophy: “We are all consenting adults here.” We use naming conventions to signal intent, and the @property decorator to manage access.
1. Naming Conventions
Section titled “1. Naming Conventions”Public (No Underscore)
Section titled “Public (No Underscore)”Available to everyone. Use this for attributes that are safe to read and write directly.
self.name = "Alice"
Protected (Single Underscore: _)
Section titled “Protected (Single Underscore: _)”A signal to other developers: “This is internal. Don’t touch it unless you know what you’re doing.” Python doesn’t actually stop you from accessing it.
self._internal_count = 0
Private (Double Underscore: __)
Section titled “Private (Double Underscore: __)”Triggers Name Mangling. Python renames the attribute internally to prevent it from being accidentally overwritten in subclasses.
self.__secret_key = "12345" -> Becomes _ClassName__secret_key.
2. The Pythonic way: @property
Section titled “2. The Pythonic way: @property”In other languages, you write get_balance() and set_balance(). This is un-Pythonic. Instead, we use the @property decorator to make a method look like a simple attribute access.
class BankAccount: def __init__(self): self._balance = 0
@property def balance(self): """The Getter""" return f"${self._balance:,.2f}"
@balance.setter def balance(self, value): """The Setter (with validation)""" if value < 0: raise ValueError("Balance cannot be negative!") self._balance = value
acc = BankAccount()acc.balance = 1000 # Calls the setterprint(acc.balance) # Calls the getter -> "$1,000.00"3. Why use Properties?
Section titled “3. Why use Properties?”- Validation: You can ensure data is correct before it’s saved.
- Computed Attributes: You can calculate a value on the fly (e.g., a
full_nameproperty that joinsfirstandlast). - Read-Only Data: If you define a
@propertybut no@setter, the attribute becomes read-only. - Forward Compatibility: You can start with a public attribute
self.age, and later change it to a@propertywithout breaking any code that uses your class.
4. Under the Hood: Descriptors
Section titled “4. Under the Hood: Descriptors”How does @property actually work? It uses a powerful Python feature called Descriptors.
When you access acc.balance, Python doesn’t find a variable. It finds a property object. This object has a __get__ method that Python calls automatically. You will learn more about descriptors in the Advanced Metaprogramming module.
5. Summary Table
Section titled “5. Summary Table”| Access Level | Syntax | Meaning |
|---|---|---|
| Public | self.x | Open to everyone. |
| Protected | self._x | Internal, but not strictly forbidden. |
| Private | self.__x | Renamed internally to avoid collisions. |
| Property | @property | Managed access via getter/setter methods. |