Skip to content

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.


Available to everyone. Use this for attributes that are safe to read and write directly. self.name = "Alice"

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

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.


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.

property_demo.py
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 setter
print(acc.balance) # Calls the getter -> "$1,000.00"

  1. Validation: You can ensure data is correct before it’s saved.
  2. Computed Attributes: You can calculate a value on the fly (e.g., a full_name property that joins first and last).
  3. Read-Only Data: If you define a @property but no @setter, the attribute becomes read-only.
  4. Forward Compatibility: You can start with a public attribute self.age, and later change it to a @property without breaking any code that uses your class.

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.


Access LevelSyntaxMeaning
Publicself.xOpen to everyone.
Protectedself._xInternal, but not strictly forbidden.
Privateself.__xRenamed internally to avoid collisions.
Property@propertyManaged access via getter/setter methods.