Encapsulation: Private Attributes and Properties
Encapsulation is the practice of hiding internal details of a class and controlling access through public methods. In Python, there are no truly private attributes, but conventions indicate access levels.
A single underscore prefix (_attr) signals 'internal use.' A double underscore prefix (__attr) triggers name mangling, making it harder to access from outside the class. The @property decorator lets you create getter/setter methods that look like attribute access.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner # Public
self._account_type = "Savings" # Protected (convention)
self.__balance = balance # Private (name mangled)
@property
def balance(self):
"""Getter for balance"""
return self.__balance
@balance.setter
def balance(self, amount):
"""Setter with validation"""
if amount < 0:
raise ValueError("Balance cannot be negative")
self.__balance = amount
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self.__balance += amount
return self.__balance
def withdraw(self, amount):
if amount > self.__balance:
raise ValueError("Insufficient funds")
self.__balance -= amount
return self.__balance
account = BankAccount("Alice", 1000)
print(account.balance) # 1000 (uses @property getter)
account.deposit(500)
print(account.balance) # 1500
account.withdraw(200)
print(account.balance) # 1300
# account.__balance # AttributeError!
# account.balance = -100 # ValueError: Balance cannot be negative - public_attr — accessible from anywhere
- _protected_attr — convention: internal use only (still accessible)
- __private_attr — name mangled to _ClassName__private_attr
- @property — turn a method into a read-only attribute
- @attr.setter — define a setter with validation logic
JavaScript
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance
@property
def balance(self):
return self.__balance
def deposit(self, amount):
self.__balance += amount
def withdraw(self, amount):
if amount > self.__balance:
print("Insufficient funds!")
return
self.__balance -= amount
acct = BankAccount("Alice", 1000)
print(f"Balance: ${acct.balance}")
acct.deposit(500)
print(f"After deposit: ${acct.balance}")
acct.withdraw(2000) - Python's philosophy is 'we're all consenting adults.' The underscore conventions are guidelines, not strict enforcement. Trust your team to follow them.
Abstract Classes
An abstract class is a class that cannot be instantiated directly — it serves as a blueprint for other classes. Abstract methods are declared but have no implementation in the base class; subclasses must provide their own implementation.
Python provides the abc module (Abstract Base Classes) to create abstract classes. Any subclass that doesn't implement all abstract methods will raise a TypeError when instantiated.
from abc import ABC, abstractmethod
class Vehicle(ABC):
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
@abstractmethod
def fuel_type(self):
"""Must be implemented by subclasses"""
pass
@abstractmethod
def start(self):
pass
# Non-abstract method — inherited as-is
def info(self):
return f"{self.year} {self.make} {self.model}"
class ElectricCar(Vehicle):
def fuel_type(self):
return "Electric"
def start(self):
return "Silently powering on..."
class GasCar(Vehicle):
def fuel_type(self):
return "Gasoline"
def start(self):
return "Vroom! Engine starting..."
# Cannot instantiate abstract class
# v = Vehicle("Generic", "Car", 2024) # TypeError!
tesla = ElectricCar("Tesla", "Model 3", 2024)
camry = GasCar("Toyota", "Camry", 2023)
for car in [tesla, camry]:
print(f"{car.info()} — {car.fuel_type()} — {car.start()}") JavaScript
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return 3.14159 * self.r ** 2
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
for shape in [Circle(5), Square(4)]:
print(f"{type(shape).__name__}: {shape.area():.2f}") - Abstract classes define a contract that subclasses must follow. They are a powerful tool for designing clean APIs and ensuring consistency across related classes.
