Chapter 4: Object-Oriented Programming (OOP) - Interview Preparation Notes
Table of Contents
- Classes and Objects
- init, Attributes, and Methods
- Encapsulation and Properties
- Inheritance and Method Overriding
- Polymorphism, Duck Typing, and ABCs
- Magic (Dunder) Methods
- @classmethod and @staticmethod
- Dataclasses and Simple Patterns
- Common Interview Questions
- Practice Problems
1. Classes and Objects
# Basic class
class BankAccount:
def __init__(self, owner: str, balance: float = 0.0) -> None:
self.owner = owner
self.balance = balance
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError("Amount must be positive")
self.balance += amount
def withdraw(self, amount: float) -> None:
if amount > self.balance:
raise ValueError("Insufficient funds")
self.balance -= amount
def get_balance(self) -> float:
return self.balance
acc = BankAccount("Alice", 100)
acc.deposit(50)Notes:
- Objects are instances of classes.
- Methods are functions defined inside a class that operate on instances.
2. init, Attributes, and Methods
class User:
# Class attribute (shared among all instances)
platform = "web"
def __init__(self, name: str, email: str) -> None:
# Instance attributes (unique per instance)
self.name = name
self.email = email
def describe(self) -> str:
return f"{self.name} <{self.email}> on {self.platform}"
# Accessing attributes
u = User("Bob", "bob@example.com")
print(u.describe())
# Updating class attribute affects all unless shadowed on instance
User.platform = "mobile"Key points:
__init__initializes new objects.- Class attributes vs instance attributes: know the difference.
3. Encapsulation and Properties
class Temperature:
def __init__(self, celsius: float) -> None:
self._celsius = celsius # convention: protected (single underscore)
@property
def celsius(self) -> float:
return self._celsius
@celsius.setter
def celsius(self, value: float) -> None:
if value < -273.15:
raise ValueError("Below absolute zero")
self._celsius = value
@property
def fahrenheit(self) -> float:
return self._celsius * 9 / 5 + 32
@fahrenheit.setter
def fahrenheit(self, value: float) -> None:
self.celsius = (value - 32) * 5 / 9Notes:
- Use properties to control access/validation.
- Name mangling:
__privatebecomes_ClassName__private.
4. Inheritance and Method Overriding
class Shape:
def area(self) -> float:
raise NotImplementedError
class Rectangle(Shape):
def __init__(self, width: float, height: float) -> None:
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
class Circle(Shape):
def __init__(self, radius: float) -> None:
self.radius = radius
def area(self) -> float:
from math import pi
return pi * self.radius * self.radius
# super() when extending behavior
class Square(Rectangle):
def __init__(self, side: float) -> None:
super().__init__(side, side)Notes:
- Prefer composition over inheritance if you only reuse small pieces.
- Use
super()to call parent methods.
5. Polymorphism, Duck Typing, and ABCs
# Duck typing: if it walks like a duck...
class FileLogger:
def log(self, msg: str) -> None:
print(f"FILE: {msg}")
class ConsoleLogger:
def log(self, msg: str) -> None:
print(f"CONSOLE: {msg}")
# Works with any object having .log()
def process(logger, message: str) -> None:
logger.log(message)
process(ConsoleLogger(), "hello")
process(FileLogger(), "world")
# ABCs enforce required methods
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, to: str, message: str) -> None:
pass
class EmailNotifier(Notifier):
def send(self, to: str, message: str) -> None:
print(f"email to {to}: {message}")Notes:
- Duck typing is idiomatic Python; use ABCs when contracts matter.
6. Magic (Dunder) Methods
class Vector:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Vector(x={self.x}, y={self.y})" # unambiguous
def __str__(self) -> str:
return f"({self.x}, {self.y})" # user-friendly
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
def __len__(self) -> int:
# Example: length as count of components
return 2
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self.x == other.x and self.y == other.yNotes:
- Implement
__repr__for debugging and logging. - Return
NotImplementedfor unsupported comparisons.
7. @classmethod and @staticmethod
from datetime import datetime
class User:
def __init__(self, name: str, joined_at: datetime) -> None:
self.name = name
self.joined_at = joined_at
@classmethod
def from_name(cls, name: str) -> "User":
# alternate constructor
return cls(name=name, joined_at=datetime.utcnow())
@staticmethod
def is_valid_name(name: str) -> bool:
return bool(name and name.strip())
u = User.from_name("Alice")
assert User.is_valid_name("Bob")Notes:
- Use
@classmethodfor alternate constructors. - Use
@staticmethodfor utilities withoutself/cls.
8. Dataclasses and Simple Patterns
from dataclasses import dataclass
@dataclass(slots=True)
class Point:
x: float
y: float
p = Point(1.0, 2.0)
# Factory pattern (simple)
class ShapeFactory:
@staticmethod
def create(shape: str, **kwargs):
shape = shape.lower()
if shape == "circle":
return Circle(kwargs["radius"]) # uses Circle from above
if shape == "rectangle":
return Rectangle(kwargs["width"], kwargs["height"]) # from above
raise ValueError("Unknown shape")
# Pythonic singleton (module-level instance or caching in __new__)
class Config:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instanceNotes:
dataclassauto-generates__init__,__repr__, comparisons.slots=Truesaves memory.- Prefer module-level singletons instead of strict patterns when possible.
9. Common Interview Questions
- Difference between class attributes and instance attributes?
- When to use inheritance vs composition?
- Explain encapsulation in Python without access modifiers.
- What are magic methods? Commonly implemented ones?
- Use cases for
@classmethodvs@staticmethod. - What is duck typing? Pros/cons.
10. Practice Problems
- BankAccount enhancements
- Add transfer between accounts with validation.
- Add
__str__to print summaries.
- Shapes hierarchy
- Implement
Trianglewith base/height and area. - Add perimeter where applicable.
- Vector arithmetic
- Implement
__sub__,__mul__(scalar), and magnitude method.
- Property validation
- Extend
Temperatureto support Kelvin with conversions.
- Notifier ABC
- Add
SMSNotifierandSlackNotifierimplementations and a router that selects by type.