Wednesday we met visibility conventions. Today we put them to work properly, then started a project that will carry us through the next several sessions.
1. Why Encapsulation Exists
Encapsulation means guarding an object’s internal state, so outside code can’t put it into a state that should be impossible.
Without it:
class BankAccount:
def __init__(self, balance):
self.balance = balance
b1 = BankAccount(5000)
b1.balance -= 10000 # nothing stops this
print(b1.balance) # -5000: a balance that should never exist
Any code, anywhere, can change the balance to anything. The rules about what a balance may be live nowhere.
2. First Fix: Methods That Enforce Rules
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def withdraw(self, amount):
if amount > self.__balance:
raise ValueError("Insufficient funds")
self.__balance -= amount
def get_balance(self):
return self.__balance
withdrawvalidates before it changes anything.get_balancelets you read the value without being able to change it.
About __balance: Python has no access modifiers. The double underscore triggers name mangling, so the name becomes _BankAccount__balance and b1.__balance fails from outside. It discourages tampering; it doesn’t make tampering impossible. Wednesday’s post has the details.
3. The Pythonic Way: @property
Getter and setter methods work, but Python offers something cleaner. @property makes a method look like an attribute.
class BankAccount:
def __init__(self, balance):
self.balance = balance # goes through the setter below
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("Balance cannot be negative")
self._balance = value
b1 = BankAccount(5000)
print(b1.balance) # reads like an attribute
b1.balance = -100 # ValueError: the rule is enforced
What’s going on:
@propertyturnsbalance()into something you read asb1.balance, without brackets.@balance.setterruns whenever someone assigns to it. The setter must use the same name as the property.- Assigning in
__init__through the property means the validation also runs when the object is created.BankAccount(-500)fails immediately.
A design trade-off worth noticing: for a real bank account, you probably wouldn’t provide a public setter at all. A balance should change only through deposit and withdraw, which say why it changed and can record it. The setter here is a clear way to learn @property. In a real design, ask whether a value should be settable at all, or only readable.
How other languages do it:
| Language | Approach |
|---|---|
| Java | Explicit getBalance() and setBalance() methods |
| C# | Built-in properties with get and set accessors, much like Python’s @property |
| Python | @property and @name.setter |
4. The Project: A CLI Calculator Built to Grow
A command-line calculator, built twice: first the novice way, then the professional way, so the effect of design principles is something you see rather than take on trust.
Requirements:
- Basic calculations: addition, subtraction and so on.
- Complex calculations: simple interest, EMI, the area of a circle.
- Extensibility: adding a new operation must not require changing code that already works.
That last requirement has a name: the Open/Closed Principle, the O in SOLID. Code should be open to extension and closed to modification. This project is how we’ll learn it.
5. Designing Before Coding
What every operation has in common:
| In plain English | Example | |
|---|---|---|
| Capability | What it can do | Every operation can calculate |
| Contents | What it needs | A radius for a circle; principal, rate and time for interest |
The relationships:
| Relationship | Statement | In code |
|---|---|---|
| Is a | An Addition is a Calculation | Inheritance |
| Has a | A Calculator has many Calculations | Composition |
The same two relationships from last week, now driving a real design.
6. Two Tools We’ll Use Next
Abstract classes
- A blueprint that says every
Calculationmust have acalculatemethod. - Built with
abc.ABCand@abstractmethod. - You can’t create an object from a class that hasn’t filled in every abstract method. Python stops you when you try to instantiate it.
Protocols
- A newer way to describe what an object must be able to do, without requiring it to inherit from anything.
- Close to an interface in Java or .NET, with one key difference:
| Java / .NET interface | Python Protocol | |
|---|---|---|
| How a class qualifies | It must declare it: implements, or : in C# | Automatically, if it has the right methods |
| Name for this | Nominal typing | Structural typing |
| When it’s checked | By the compiler | By a type checker such as mypy, not at runtime (unless you add @runtime_checkable) |
A protocol is duck typing made explicit: “if it has a calculate method, it’s a calculation.”
7. Project Setup
The project starts with a folder and a README.md holding the goals and the low-level class diagram. Design is written down before code is written. That habit is part of what this project teaches.
✅ Action Items
- Break the open
BankAccountby setting a negative balance, then fix it three ways: with methods, with__balance, and with@property. - Write a setter that validates, and confirm it runs when you create the object as well as when you assign later.
- Sketch the calculator design yourself before next session: the base calculation, three concrete operations including EMI, and the
Calculatorthat holds them. Label every relationship as is-a or has-a. - Read about
typing.Protocol, and be ready to explain how it differs from a Java interface.
⏭️ Next up: the calculator, novice version first.
