================================================================================
PYTHON OBJECT-ORIENTED PROGRAMMING - MID-LEVEL DEVELOPER INTERVIEW QUESTIONS
================================================================================
Q1. CLASS AND OBJECT BASICS
---------------------------
Question: Create a simple BankAccount class with methods to deposit, withdraw, and
check balance.
Solution:
```python
class BankAccount:
def __init__(self, account_number, owner_name, initial_balance=0):
self.account_number = account_number
self.owner_name = owner_name
self.balance = initial_balance
self.transactions = []
def deposit(self, amount):
if amount > 0:
self.balance += amount
self.transactions.append(f"Deposit: +${amount}")
return True
return False
def withdraw(self, amount):
if amount > 0 and self.balance >= amount:
self.balance -= amount
self.transactions.append(f"Withdrawal: -${amount}")
return True
return False
def get_balance(self):
return self.balance
def get_transaction_history(self):
return self.transactions
# Usage
account = BankAccount("12345", "John Doe", 1000)
account.deposit(500)
account.withdraw(200)
print(f"Balance: ${account.get_balance()}")
print("Transactions:", account.get_transaction_history())
```
Q2. INHERITANCE
---------------
Question: Create a hierarchy of vehicles: Vehicle (base class), Car, and Motorcycle
(derived classes).
Solution:
```python
class Vehicle:
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
self.is_running = False
def start_engine(self):
self.is_running = True
return f"{self.brand} {self.model} engine started"
def stop_engine(self):
self.is_running = False
return f"{self.brand} {self.model} engine stopped"
def get_info(self):
return f"{self.year} {self.brand} {self.model}"
class Car(Vehicle):
def __init__(self, brand, model, year, num_doors):
super().__init__(brand, model, year)
self.num_doors = num_doors
def get_info(self):
return f"{super().get_info()} - {self.num_doors} doors"
def honk(self):
return "Car horn: Beep! Beep!"
class Motorcycle(Vehicle):
def __init__(self, brand, model, year, engine_size):
super().__init__(brand, model, year)
self.engine_size = engine_size
def get_info(self):
return f"{super().get_info()} - {self.engine_size}cc"
def wheelie(self):
return "Motorcycle doing a wheelie!"
# Usage
car = Car("Toyota", "Camry", 2022, 4)
motorcycle = Motorcycle("Honda", "CBR600", 2021, 600)
print(car.get_info())
print(car.start_engine())
print(car.honk())
print(motorcycle.get_info())
print(motorcycle.start_engine())
print(motorcycle.wheelie())
```
Q3. ENCAPSULATION
-----------------
Question: Create a Student class with private attributes and public methods to
access them.
Solution:
```python
class Student:
def __init__(self, name, age, student_id):
self._name = name # Protected attribute
self._age = age # Protected attribute
self.__student_id = student_id # Private attribute
self.__grades = [] # Private attribute
# Getter methods
def get_name(self):
return self._name
def get_age(self):
return self._age
def get_student_id(self):
return self.__student_id
def get_grades(self):
return self.__grades.copy() # Return a copy to prevent modification
# Setter methods with validation
def set_name(self, name):
if isinstance(name, str) and len(name.strip()) > 0:
self._name = name.strip()
else:
raise ValueError("Name must be a non-empty string")
def set_age(self, age):
if isinstance(age, int) and 0 <= age <= 120:
self._age = age
else:
raise ValueError("Age must be between 0 and 120")
def add_grade(self, grade):
if isinstance(grade, (int, float)) and 0 <= grade <= 100:
self.__grades.append(grade)
else:
raise ValueError("Grade must be between 0 and 100")
def get_average_grade(self):
if not self.__grades:
return 0
return sum(self.__grades) / len(self.__grades)
# Usage
student = Student("Alice", 20, "S12345")
student.add_grade(85)
student.add_grade(92)
student.add_grade(78)
print(f"Name: {student.get_name()}")
print(f"Age: {student.get_age()}")
print(f"Student ID: {student.get_student_id()}")
print(f"Grades: {student.get_grades()}")
print(f"Average: {student.get_average_grade():.2f}")
```
Q4. POLYMORPHISM
----------------
Question: Create different shapes (Circle, Rectangle, Triangle) that can calculate
their area and perimeter.
Solution:
```python
from abc import ABC, abstractmethod
import math
class Shape(ABC):
@abstractmethod
def calculate_area(self):
pass
@abstractmethod
def calculate_perimeter(self):
pass
def get_info(self):
return f"{self.__class__.__name__} - Area: {self.calculate_area():.2f},
Perimeter: {self.calculate_perimeter():.2f}"
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def calculate_area(self):
return math.pi * self.radius ** 2
def calculate_perimeter(self):
return 2 * math.pi * self.radius
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
def calculate_perimeter(self):
return 2 * (self.width + self.height)
class Triangle(Shape):
def __init__(self, side1, side2, side3):
self.side1 = side1
self.side2 = side2
self.side3 = side3
def calculate_area(self):
# Using Heron's formula
s = (self.side1 + self.side2 + self.side3) / 2
return math.sqrt(s * (s - self.side1) * (s - self.side2) * (s -
self.side3))
def calculate_perimeter(self):
return self.side1 + self.side2 + self.side3
# Usage - demonstrating polymorphism
shapes = [
Circle(5),
Rectangle(4, 6),
Triangle(3, 4, 5)
]
for shape in shapes:
print(shape.get_info())
```
Q5. STATIC METHODS AND CLASS METHODS
------------------------------------
Question: Create a MathUtils class with static and class methods for mathematical
operations.
Solution:
```python
class MathUtils:
PI = 3.14159
@staticmethod
def add(a, b):
return a + b
@staticmethod
def multiply(a, b):
return a * b
@staticmethod
def is_even(number):
return number % 2 == 0
@classmethod
def get_pi(cls):
return cls.PI
@classmethod
def circle_area(cls, radius):
return cls.PI * radius ** 2
@classmethod
def create_from_string(cls, number_string):
try:
return float(number_string)
except ValueError:
return None
# Usage
print(MathUtils.add(5, 3)) # 8
print(MathUtils.multiply(4, 7)) # 28
print(MathUtils.is_even(10)) # True
print(MathUtils.get_pi()) # 3.14159
print(MathUtils.circle_area(5)) # 78.53975
print(MathUtils.create_from_string("3.14")) # 3.14
```
Q6. PROPERTY DECORATORS
-----------------------
Question: Create a Temperature class that can convert between Celsius and
Fahrenheit using properties.
Solution:
```python
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
self._celsius = value
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
def __str__(self):
return f"{self._celsius}°C ({self.fahrenheit}°F)"
# Usage
temp = Temperature(25)
print(temp) # 25°C (77.0°F)
temp.fahrenheit = 100
print(temp) # 37.77777777777778°C (100.0°F)
temp.celsius = 0
print(temp) # 0°C (32.0°F)
```
Q7. SPECIAL METHODS (MAGIC METHODS)
-----------------------------------
Question: Create a Book class with special methods for string representation,
comparison, and arithmetic operations.
Solution:
```python
class Book:
def __init__(self, title, author, pages, price):
self.title = title
self.author = author
self.pages = pages
self.price = price
def __str__(self):
return f"'{self.title}' by {self.author}"
def __repr__(self):
return f"Book('{self.title}', '{self.author}', {self.pages}, {self.price})"
def __len__(self):
return self.pages
def __lt__(self, other):
return self.price < other.price
def __eq__(self, other):
return (self.title == other.title and
self.author == other.author and
self.pages == other.pages)
def __add__(self, other):
# Combine two books (create a collection)
return BookCollection([self, other])
def __contains__(self, item):
return item.lower() in self.title.lower() or item.lower() in
self.author.lower()
class BookCollection:
def __init__(self, books):
self.books = books
def __str__(self):
return f"Collection of {len(self.books)} books"
def total_pages(self):
return sum(book.pages for book in self.books)
# Usage
book1 = Book("Python Programming", "John Smith", 300, 29.99)
book2 = Book("Data Science", "Jane Doe", 250, 34.99)
print(str(book1)) # 'Python Programming' by John Smith
print(len(book1)) # 300
print(book1 < book2) # True
print("Python" in book1) # True
collection = book1 + book2
print(collection) # Collection of 2 books
print(collection.total_pages()) # 550
```
Q8. ERROR HANDLING IN CLASSES
-----------------------------
Question: Create a robust Calculator class with proper error handling.
Solution:
```python
class CalculatorError(Exception):
"""Custom exception for calculator errors"""
pass
class Calculator:
def __init__(self):
self.history = []
def add(self, a, b):
try:
result = float(a) + float(b)
self.history.append(f"{a} + {b} = {result}")
return result
except (ValueError, TypeError):
raise CalculatorError("Invalid input: both arguments must be numbers")
def subtract(self, a, b):
try:
result = float(a) - float(b)
self.history.append(f"{a} - {b} = {result}")
return result
except (ValueError, TypeError):
raise CalculatorError("Invalid input: both arguments must be numbers")
def multiply(self, a, b):
try:
result = float(a) * float(b)
self.history.append(f"{a} * {b} = {result}")
return result
except (ValueError, TypeError):
raise CalculatorError("Invalid input: both arguments must be numbers")
def divide(self, a, b):
try:
if float(b) == 0:
raise CalculatorError("Division by zero is not allowed")
result = float(a) / float(b)
self.history.append(f"{a} / {b} = {result}")
return result
except (ValueError, TypeError):
raise CalculatorError("Invalid input: both arguments must be numbers")
def get_history(self):
return self.history
def clear_history(self):
self.history.clear()
# Usage with error handling
calc = Calculator()
try:
print(calc.add(5, 3)) # 8.0
print(calc.multiply(4, 2.5)) # 10.0
print(calc.divide(10, 2)) # 5.0
print(calc.divide(5, 0)) # Raises CalculatorError
except CalculatorError as e:
print(f"Calculator error: {e}")
print("History:", calc.get_history())
```
PRACTICAL EXERCISES:
====================
Exercise 1: Library Management System
------------------------------------
```python
class Book:
def __init__(self, isbn, title, author, year):
self.isbn = isbn
self.title = title
self.author = author
self.year = year
self.is_borrowed = False
class Library:
def __init__(self):
self.books = {}
self.borrowers = {}
def add_book(self, book):
self.books[book.isbn] = book
def borrow_book(self, isbn, borrower_name):
if isbn in self.books:
book = self.books[isbn]
if not book.is_borrowed:
book.is_borrowed = True
if borrower_name not in self.borrowers:
self.borrowers[borrower_name] = []
self.borrowers[borrower_name].append(isbn)
return True
return False
def return_book(self, isbn):
if isbn in self.books:
book = self.books[isbn]
book.is_borrowed = False
# Remove from borrower's list
for borrower, books in self.borrowers.items():
if isbn in books:
books.remove(isbn)
return True
return False
```
Exercise 2: Bank Account with Interest
-------------------------------------
```python
class BankAccount:
def __init__(self, account_number, balance=0, interest_rate=0.01):
self.account_number = account_number
self.balance = balance
self.interest_rate = interest_rate
def add_interest(self):
interest = self.balance * self.interest_rate
self.balance += interest
return interest
class SavingsAccount(BankAccount):
def __init__(self, account_number, balance=0):
super().__init__(account_number, balance, 0.02) # Higher interest rate
def withdraw(self, amount):
if self.balance - amount >= 100: # Minimum balance requirement
self.balance -= amount
return True
return False
```
KEY CONCEPTS TO REMEMBER:
=========================
1. **Encapsulation**: Bundling data and methods that operate on that data
2. **Inheritance**: Creating new classes based on existing ones
3. **Polymorphism**: Using objects of different classes through a common interface
4. **Abstraction**: Hiding complex implementation details
5. **Composition**: Building complex objects from simpler ones
6. **Association**: Relationships between objects
BEST PRACTICES:
===============
1. Use meaningful class and method names
2. Keep classes focused on a single responsibility
3. Use inheritance for "is-a" relationships
4. Use composition for "has-a" relationships
5. Implement proper error handling
6. Use docstrings to document classes and methods
7. Follow PEP 8 naming conventions
8. Use properties for controlled access to attributes