Phase 3 · Object-Oriented PythonModule 14~36 min read

Encapsulation, Properties & Class Methods

Control access with conventions, @property, and class/static methods.

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 __private naming
  • 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:

Access by convention
namePublicAnyone can use it — the default
_nameProtected (by convention)"Internal — please don't touch"
__namePrivate (name-mangled)Hidden from outside & subclasses

Note

A single underscore (_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:

account.py
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)     # 150

Tip

This is the Pythonic answer to getters and setters: start with a plain public attribute, and only convert it to a @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 neither self nor cls. It's just a plain function that logically belongs with the class.
pizza.py
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.