OSPython.015: Encapsulation Basics

Minimal black and neon-green illustration representing Python encapsulation and protected class data.

Encapsulation keeps an object’s data and the code that manages that data together. In Python, it is less about absolute access control and more about designing a clear, safe interface for changing object state.

In OSPython.014: Polymorphism Basics, we saw different objects respond to the same method name in different ways. Encapsulation focuses on a different question: how should outside code interact with the data inside one object?

Start with a simple class

class Miner:
    def __init__(self, model, temperature):
        self.model = model
        self.temperature = temperature

miner = Miner("S21", 72)

print(miner.model)
print(miner.temperature)

Both attributes are public, so code outside the class can read or replace them directly. That is simple, but it also means nothing stops another part of the program from assigning an impossible value such as -500 degrees.

Video 1: Public, protected, and private members

CodeLucky explains Python encapsulation, public/protected/private naming, name mangling, and the property decorator.

Python uses naming conventions

Python does not enforce access modifiers exactly like languages such as Java or C++. Instead, Python relies heavily on naming conventions.

  • name — public attribute; normal outside access is expected.
  • _name — protected-style convention; outside code can access it, but the leading underscore says “treat this as internal.”
  • __name — private-style name that triggers name mangling inside the class.
class Wallet:
    def __init__(self, owner, balance):
        self.owner = owner
        self._network = "Bitcoin"
        self.__balance = balance

wallet = Wallet("Alex", 2500)

print(wallet.owner)
print(wallet._network)

owner is public. _network is still accessible, but its underscore signals that callers should avoid depending on it directly. The double-underscore attribute behaves differently.

Double underscores and name mangling

When an attribute begins with two underscores, Python rewrites its internal name. This is called name mangling.

class Wallet:
    def __init__(self, balance):
        self.__balance = balance

wallet = Wallet(2500)

# This raises AttributeError:
# print(wallet.__balance)

# Python internally mangles the name:
print(wallet._Wallet__balance)

Name mangling makes accidental access harder, but it is not a security boundary. A determined caller can still reach the mangled attribute. The main goal is API design: clearly communicate which values outside code should use directly and which values the class should manage itself.

Video 2: Encapsulation with @property

Sumantra Codes demonstrates encapsulation, name mangling, properties, setters, and validation in Python classes.

Use a property to control updates

A property lets callers use normal attribute syntax while the class runs logic behind the scenes.

class Miner:
    def __init__(self, model, temperature):
        self.model = model
        self._temperature = temperature

    @property
    def temperature(self):
        return self._temperature

    @temperature.setter
    def temperature(self, value):
        if value < -40 or value > 120:
            raise ValueError("Temperature is outside the allowed range")
        self._temperature = value

miner = Miner("S21", 72)

print(miner.temperature)
miner.temperature = 78
print(miner.temperature)

Outside code still writes miner.temperature = 78, but the setter gets a chance to validate the new value first. That is a practical form of encapsulation: the class protects its own rules.

Why properties are useful

  • Validate values before saving them.
  • Prevent invalid object state.
  • Expose a clean interface without forcing callers to use get_... and set_... methods everywhere.
  • Change the internal implementation later without changing the code that uses the class.

A game example

class Player:
    def __init__(self, health):
        self._health = 0
        self.health = health

    @property
    def health(self):
        return self._health

    @health.setter
    def health(self, value):
        if value < 0:
            self._health = 0
        elif value > 100:
            self._health = 100
        else:
            self._health = value

player = Player(100)
player.health = 135
print(player.health)   # 100

player.health = -10
print(player.health)   # 0

The class owns the health rules. Other parts of the game can request a new health value, but the Player object decides what values are valid.

Video 3: Getters, setters, and the property decorator

Matt Macarty focuses on Python’s property decorator and how getters and setters support encapsulation.

Encapsulation is not “make everything private”

Good encapsulation is selective. A class should expose the operations that callers actually need while keeping implementation details behind a stable interface.

class MiningRig:
    def __init__(self, name):
        self.name = name
        self._online = False

    def start(self):
        self._online = True

    def stop(self):
        self._online = False

    @property
    def online(self):
        return self._online

rig = MiningRig("Rack-A1")
rig.start()

print(rig.name)
print(rig.online)

Callers can start and stop the rig through methods and read its current status through a property. They do not need to know how the internal state is stored.

Common beginner mistakes

  • Assuming _name is truly inaccessible from outside the class.
  • Assuming __name creates strong security. It mainly triggers name mangling.
  • Writing getters and setters that add no validation or behavior when a normal public attribute would be simpler.
  • Changing internal attributes directly from unrelated code instead of using the class interface.
  • Forgetting to use a backing attribute such as _temperature inside a property setter, which can accidentally cause recursion.

Practice

  1. Create a Battery class with a protected-style _charge attribute.
  2. Create a charge property that returns the current percentage.
  3. Add a setter that accepts only values from 0 through 100.
  4. Raise ValueError for values outside that range.
  5. Create one object, set valid and invalid values, and observe the results.

Key takeaway

Encapsulation gives an object responsibility for protecting its own state and presenting a clean interface to the rest of the program. Python usually does this with naming conventions, name mangling, methods, and especially properties. Review OSPython.012: Classes and Objects, OSPython.013: Inheritance Basics, and OSPython.014: Polymorphism Basics if you want the full OOP progression in order.

Leave a comment