8 min read
Encapsulation and abstraction
Hiding the wiring
When you use a phone you press a few buttons; you do not touch the circuits inside. That split, a simple surface over a protected interior, is what these two ideas give your objects. Encapsulation keeps an object's data tucked behind its methods so nothing outside can quietly corrupt it. Abstraction goes further and lets you describe what a kind of object must be able to do while leaving the how to each specific object. Both make programs easier to trust and easier to change.
Public, protected and private
Python marks how freely an attribute is meant to be touched using underscores in its name. There are three levels by convention.
- A plain name like
balanceis public: anyone may read or change it. - A single leading underscore,
_balance, marks it protected: a polite sign meaning "internal, please do not touch from outside". Python does not stop you, but you have been warned. - A double leading underscore,
__balance, is private: Python actively mangles the name so that code outside the class cannot reach it by the obvious path.
class GiftCard:
def __init__(self, amount):
self.__balance = amount # private: kept inside the class
def get_balance(self):
return self.__balance
def spend(self, amount):
if 0 < amount <= self.__balance:
self.__balance = self.__balance - amount
else:
print("Invalid amount")
card = GiftCard(50)
card.spend(20)
print(card.get_balance()) # 30
print(card.__balance) # AttributeError: no such attribute
The last line fails on purpose. Because __balance is private, the outside world cannot poke it directly and set the card to a silly value. The only way in is through spend, which checks the amount first.
Getters, setters and why they guard
Private data is reached through getter and setter methods, exactly as you met earlier. The reason to force everything through them is control. spend is really a guarded setter: it refuses an amount that is negative or larger than the balance, so the card can never fall into an impossible state. If the balance were public, any stray line anywhere could set it to a negative number and nobody would notice until much later.
Tip
Encapsulation is not about secrecy for its own sake. It is about giving each object one guarded door instead of many open windows, so that when something does go wrong, there are only a few places to look. The fewer ways there are to change a value, the easier it is to keep that value correct.
Learn more
Abstraction is the other half. Sometimes you want to promise that every object of a certain family will provide a method, without writing that method in the parent, because each child does it differently. Python offers abstract base classes through the abc module for exactly this.
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def pay(self, amount):
pass # no body: each child must supply one
class CardPayment(PaymentMethod):
def pay(self, amount):
return f"Charged {amount} to the card"
class CashPayment(PaymentMethod):
def pay(self, amount):
return f"Took {amount} in cash"PaymentMethod inherits from ABC and marks pay with @abstractmethod, giving it no real body. This does two things. You cannot create a bare PaymentMethod() at all, because it is incomplete. And any child is forced to provide its own pay, or Python refuses to build it. The base class states the contract, "a payment method must be payable", and abstraction leaves the details to each kind. The @ line here is a decorator; you will meet that syntax properly later.
for method in (CardPayment(), CashPayment()):
print(method.pay(30))
# Charged 30 to the card
# Took 30 in cashEncapsulation hides the inside so data stays safe; abstraction fixes the outside so every family member honours the same contract. Together they let you build large programs out of parts you can trust without reading their insides.
Prefer to see it explained? Here is the recorded Code for Albania lecture on this topic.
Try this now
Write a class GiftCard like the one above, storing the balance in a private __balance. Give it a spend(amount) method that only subtracts when 0 < amount <= self.__balance. Make a card worth 50, spend 20, print the balance through a getter, then try card.__balance directly and watch it fail. That failure is the encapsulation working.
Check yourself
- What is the practical difference between a single underscore
_nameand a double underscore__name, and which one does Python actually enforce? - The
spendmethod refuses a negative or oversized amount. Explain how making__balanceprivate, rather than public, is what makes that guard meaningful. PaymentMethodis an abstract base class with an abstractpaymethod. What two things does marking it that way prevent, and why is that useful when other people write new payment types?
Where this lesson comes from
Built from
- Data Structures and Algorithms
alphaPlan courses are built from taught programmes rather than invented for the web. Where a claim rests on an outside standard or a reported case, it is named above so you can check it rather than take our word for it.
This course was developed by alphaPlan Center from programs delivered in partnership with the American Corners network.
Found something unclear, outdated or improvable? Suggest an improvement