Python Encapsulation Examples
Short, runnable programs that show python encapsulation: public, protected and private in action. Read the explanation, press Run, then change the code and try your own ideas. New to the topic? Start with the Python Encapsulation: Public, Protected and Private lesson.
1One setter-style method guards every change
__init__ reuses set_target, so the temperature rule is written once and covers both creating the object and changing it later. The limits are class constants.
class Thermostat:
MIN_TEMP = 10
MAX_TEMP = 30
def __init__(self, target=20):
self.set_target(target)
def set_target(self, value):
if not self.MIN_TEMP <= value <= self.MAX_TEMP:
raise ValueError(f"target must be {self.MIN_TEMP}-{self.MAX_TEMP}, got {value}")
self._target = value
def get_target(self):
return self._target
home = Thermostat()
home.set_target(23)
print(home.get_target())
for bad in (5, 45):
try:
home.set_target(bad)
except ValueError as err:
print("Refused:", err)
print(home.get_target())Expected output23
Refused: target must be 10-30, got 5
Refused: target must be 10-30, got 45
23
2Name mangling in action
Inside the class, self.__combo works normally. From outside, the written name does not exist, but the mangled name _Locker__combo does. dir() shows what Python really stored.
class Locker:
def __init__(self, combo):
self.__combo = combo
self.opened = False
def try_open(self, guess):
self.opened = guess == self.__combo
return self.opened
locker = Locker("12-34-56")
print(locker.try_open("00-00-00"))
print(locker.try_open("12-34-56"))
print([name for name in dir(locker) if "combo" in name])
print(hasattr(locker, "__combo"), hasattr(locker, "_Locker__combo"))Expected outputFalse
True
['_Locker__combo']
False True
3Internal helper methods use an underscore too
The underscore convention applies to methods as well. _with_fee is a detail of how spend works, so callers are not expected to use it.
class Wallet:
FEE = 1
def __init__(self, cash):
self._cash = cash
def _with_fee(self, amount):
return amount + self.FEE
def spend(self, amount):
total = self._with_fee(amount)
if total > self._cash:
return False
self._cash -= total
return True
def get_cash(self):
return self._cash
wallet = Wallet(25)
print(wallet.spend(10), wallet.get_cash())
print(wallet.spend(10), wallet.get_cash())
print(wallet.spend(10), wallet.get_cash())Expected outputTrue 14
True 3
False 3
4Hide how the data is stored
Outside code only uses add, remove and count. The dictionary inside could become a list or a database later, and no caller would need to change.
class Inventory:
def __init__(self):
self._items = {}
def add(self, name, qty=1):
self._items[name] = self.count(name) + qty
def remove(self, name, qty=1):
have = self.count(name)
if qty > have:
raise ValueError(f"cannot remove {qty} {name}: only {have} in stock")
self._items[name] = have - qty
def count(self, name):
return self._items.get(name, 0)
shop = Inventory()
shop.add("apple", 5)
shop.remove("apple", 2)
print(shop.count("apple"), shop.count("pear"))
try:
shop.remove("apple", 10)
except ValueError as err:
print(err)Expected output3 0
cannot remove 10 apple: only 3 in stock
5Reaching past the interface breaks the promise
The method refuses an impossible withdrawal. Writing to _balance directly is allowed by Python, but it skips that protection and leaves the object invalid.
class BankAccount:
def __init__(self, balance):
self._balance = balance
def withdraw(self, amount):
if amount > self._balance:
raise ValueError("not enough money")
self._balance -= amount
def get_balance(self):
return self._balance
acct = BankAccount(100)
try:
acct.withdraw(500)
except ValueError as err:
print("Through the method:", err)
acct._balance = -500
print("Bypassing the method:", acct.get_balance())Expected outputThrough the method: not enough money
Bypassing the method: -500
6Return a copy of internal data
leaky_songs() returns the real list, so an outsider can add an ad to the playlist. songs() returns a copy, so the same trick changes only the copy.
class Playlist:
def __init__(self):
self._songs = []
def add(self, title):
self._songs.append(title)
def leaky_songs(self):
return self._songs
def songs(self):
return list(self._songs)
mix = Playlist()
mix.add("Intro")
mix.leaky_songs().append("Ad break")
mix.songs().append("Another ad")
print(mix.songs())Expected output['Intro', 'Ad break']
Practise next
Python Encapsulation: Public, Protected and Private exercises → Back to the lesson
Python Exceptions: try, except, else and finally examplesPython Class Attributes vs Instance Attributes examplesPython @property: Getters and Setters examples