What you'll learn
Good objects protect their own data and expose a clean interface. This module covers Python's approach to encapsulation — its naming conventions, the elegant @property decorator, and the two special kinds of method: @classmethod and @staticmethod.
By the end you'll be able to:
- Signal intent with public,
_protected, and__privatenaming - Add validation and computed attributes with
@property - Write factory methods with
@classmethod - Add utility functions to a class with
@staticmethod
Access conventions
Python has no true private keyword — it trusts developers, following the motto "we're all consenting adults." Instead, it uses naming conventions to signal intent:
Note
_balance) is just a hint — "this is internal." A double underscore (__balance) triggers name mangling, which actually renames it to make accidental access from outside harder. In practice, a single underscore is by far the most common.@property
You'll often want an attribute that's computed or validated — but you don't want callers writing get_balance() and set_balance() everywhere. The @property decorator lets a method be accessed like an attribute, so you get clean syntax and full control:
class Account:
def __init__(self, balance):
self._balance = balance # "internal" by convention
@property
def balance(self): # a getter — used like an attribute
return self._balance
@balance.setter
def balance(self, amount): # a setter with validation
if amount < 0:
raise ValueError("Balance can't be negative")
self._balance = amount
acc = Account(100)
print(acc.balance) # 100 — no parentheses! reads like an attribute
acc.balance = 150 # calls the setter
print(acc.balance) # 150Tip
@property if you later need validation or computation — without changing any code that uses it. That's a superpower Java-style getters don't give you.class methods & static methods
Not every method acts on a single instance. Python has two decorators for the exceptions:
@classmethod— receives the class (cls) instead of an instance. Perfect for factory methods that build and return preconfigured objects.@staticmethod— receives neitherselfnorcls. It's just a plain function that logically belongs with the class.
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
@classmethod
def margherita(cls): # a factory: builds a preset Pizza
return cls(["tomato", "mozzarella"])
@staticmethod
def is_valid_topping(name): # a plain helper — no self/cls
return name in {"tomato", "cheese", "mushroom", "mozzarella"}
p = Pizza.margherita()
print(p.toppings)
print(Pizza.is_valid_topping("mushroom"))
print(Pizza.is_valid_topping("pineapple"))Recap & quick check
Key takeaways
- Python signals access by convention: public, _protected (a hint), __private (name-mangled).
- @property lets a method be used like an attribute — ideal for validation and computed values.
- Start with public attributes; upgrade to @property later without breaking callers.
- @classmethod takes cls and is great for factory methods; @staticmethod takes neither self nor cls.
Quick check
1. What does a leading underscore (_name) mean in Python?
2. What does @property let you do?
3. What does @classmethod receive as its first argument?
4. When would you use @staticmethod?
5. Why prefer starting with a public attribute over getters/setters?
Great — your classes now protect their data cleanly. Next up: Module 15 — Inheritance & Polymorphism.