Encapsulation and Inheritance
📂 Phase 4: OOP Concepts, File Handling, Exception Handling (Days 16–20) · Python ProgrammingEncapsulation and inheritance are two of the four core pillars of object-oriented programming. Encapsulation controls who can access an object's data, while inheritance lets one class reuse and extend the behavior of another — together they are the foundation for writing organized, maintainable class hierarchies.
Encapsulation: Bundling and Protecting Data
Encapsulation means bundling data and the methods that operate on it inside a class, while restricting direct, uncontrolled access to that data from outside the class.
Public, Protected, and Private Attributes
| Convention | Syntax | Meaning |
|---|---|---|
| Public | self.name | Freely accessible from anywhere — the default in Python |
| Protected | self._name | A convention signaling "internal use only" — not enforced by Python, just a hint to other developers |
| Private | self.__name | Name-mangled by Python to discourage (but not fully prevent) outside access |
class Account:
def __init__(self, balance):
self.__balance = balance # "private" — double underscore prefix
def get_balance(self):
return self.__balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
else:
print("Deposit amount must be positive")
acc = Account(1000)
print(acc.get_balance()) # 1000
# print(acc.__balance) # AttributeError — name has been mangled internally
Unlike Java's private keyword, Python does not truly enforce privacy — double-underscore attributes are "name-mangled" to _ClassName__attribute internally, which can technically still be accessed if you know the mangled name. Python relies on developer discipline and convention far more than strict access control.
Getters and Setters
class Account:
def __init__(self, balance):
self.__balance = balance
def get_balance(self):
return self.__balance
def set_balance(self, amount):
if amount >= 0:
self.__balance = amount
else:
print("Balance cannot be negative")
acc = Account(500)
acc.set_balance(-100) # rejected, prints the warning
acc.set_balance(800) # accepted
print(acc.get_balance()) # 800
Inheritance: Reusing and Extending a Class
Inheritance lets a new class (the child or subclass) acquire the attributes and methods of an existing class (the parent or superclass), establishing an "is-a" relationship.
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating")
class Dog(Animal): # Dog inherits from Animal
def bark(self):
print(f"{self.name} says Woof!")
my_dog = Dog("Rex")
my_dog.eat() # Rex is eating — inherited from Animal
my_dog.bark() # Rex says Woof! — defined directly in Dog
The super() Function
super() lets a child class call a method from its parent class explicitly — most commonly used inside a child's own __init__ to reuse the parent's setup logic.
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # let Animal handle the name attribute
self.breed = breed
my_dog = Dog("Rex", "Labrador")
print(my_dog.name) # Rex
print(my_dog.breed) # Labrador
Method Overriding
A child class can redefine a method that already exists in the parent class, replacing the parent's behavior with its own.
class Animal:
def speak(self):
print("Some generic animal sound")
class Cat(Animal):
def speak(self): # overrides Animal's version
print("Meow")
Animal().speak() # Some generic animal sound
Cat().speak() # Meow
Interview tip: "Does Python support multiple inheritance?" — Yes, unlike Java. A class can inherit from more than one parent: class Child(Parent1, Parent2). Python resolves potential conflicts using the Method Resolution Order (MRO), which interviewers sometimes ask about by name for stronger candidates.