Unit 3: OOP concepts - Subjective Questions
ECAP776 • Practice Questions with Detailed Answers
20 questions
Define object-oriented programming. Explain its major features in the context of Python.
Object-oriented programming (OOP) is a programming approach in which software is organized around objects that combine data and behavior.
Major OOP features are:
- Classes and objects: A class is a blueprint, while an object is an instance of that class.
- Encapsulation: Data and the methods operating on it are grouped together, and access to internal state is controlled.
- Inheritance: A new class can reuse and extend the attributes and methods of an existing class.
- Polymorphism: The same interface or method name can produce different behavior for different objects.
- Abstraction: Unnecessary implementation details are hidden while essential operations are exposed.
- Dynamic binding: Python determines the method to execute at runtime based on the actual object.
Distinguish between a class and an object with a suitable Python example.
- A class is a user-defined blueprint that specifies attributes and methods.
- An object is a concrete instance of a class, with its own identity and state.
- Defining a class does not by itself create individual instance data; constructing objects does.
Example:
class Student:
def __init__(self, name):
self.name = name
def introduce(self):
return f"I am {self.name}"
first = Student("Asha")
second = Student("Ravi")
Here, Student is the class, while first and second are separate objects. Both share the behavior defined by introduce(), but each stores a different value for name.
Explain the purpose of the __init__() method and the self parameter in Python classes.
__init__() is an initializer method automatically invoked after a new object has been created. It is commonly used to establish the object's initial state.
self refers to the current instance on which a method is operating. Through self, an instance method can read or modify instance attributes and call other instance methods.
Example:
class Account:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
account = Account("Meera", 1000)
account.deposit(500)
In this example, self.owner and self.balance belong to a particular Account object. The name self is conventional rather than a reserved keyword, but using it is strongly recommended.
Describe instance attributes, class attributes, instance methods, class methods, and static methods.
- Instance attributes belong to individual objects and are generally assigned through
self, such asself.name. - Class attributes belong to the class and are shared unless an instance shadows them.
- Instance methods receive
selfand can access both instance and class state. - Class methods use the
@classmethoddecorator, receivecls, and commonly modify class state or provide alternative constructors. - Static methods use
@staticmethod. They receive neitherselfnorclsautomatically and usually implement utility behavior related to the class.
Example:
class Employee:
company = "ABC"
def __init__(self, name):
self.name = name
@classmethod
def change_company(cls, value):
cls.company = value
@staticmethod
def valid_name(value):
return bool(value.strip())
The most suitable method type depends on whether the operation requires an object, the class itself, or neither.
Define encapsulation and explain why it is important in object-oriented programming.
Encapsulation is the practice of combining data and related methods within a class while controlling how the object's internal state is accessed or changed.
Its importance includes:
- Data protection: Prevents uncontrolled modification of sensitive state.
- Validation: Changes can be accepted only when they satisfy required rules.
- Maintainability: Internal implementation can change without affecting client code that uses the public interface.
- Reduced coupling: Other parts of a program depend on documented operations rather than internal representation.
- Clear responsibility: A class manages the validity of its own objects.
Python implements encapsulation mainly through naming conventions, name mangling, properties, and carefully designed methods rather than absolute access restrictions.
Explain public, protected, and private members in Python. How does Python's approach differ from strict access control?
Python represents access intentions primarily through naming conventions:
- Public member: A name such as
balancecan be accessed normally from anywhere. - Protected member: A name such as
_balanceindicates that it is intended for internal use by the class and its subclasses. This is a convention and is not enforced. - Private member: A name such as
__balancetriggers name mangling. Python internally changes it to a form resembling_ClassName__balance.
Example:
class Account:
def __init__(self):
self.owner = "Asha"
self._branch_code = 101
self.__balance = 5000
Unlike languages with strict private or protected keywords, Python generally follows the principle of responsible access. Name mangling discourages accidental access and avoids naming conflicts, but it does not provide complete security.
What is name mangling in Python? Describe its purpose, behavior, and limitations.
When an attribute begins with two underscores and does not end with two underscores, Python applies name mangling.
For example, __value inside Sample is internally represented approximately as _Sample__value.
Its purposes are:
- To reduce accidental access or modification from outside the class.
- To prevent unintended name clashes when subclasses define members with the same spelling.
Example:
class Sample:
def __init__(self):
self.__value = 10
def get_value(self):
return self.__value
Normal access through object.__value fails, but the value may still be reached through object._Sample__value. Therefore, name mangling is not a security mechanism and does not make data truly inaccessible. It is primarily a mechanism for avoiding accidental interference.
Explain how properties support encapsulation in Python. Illustrate your answer with validation of an attribute.
A property allows a method to be accessed using attribute syntax. It provides controlled reading, assignment, or deletion while preserving a simple public interface.
Example:
class Product:
def __init__(self, price):
self.price = price
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("Price cannot be negative")
self._price = value
item = Product(250)
item.price = 300
The setter validates every assigned value, including the value supplied during initialization. Client code continues to use item.price rather than explicit getter and setter method calls. This permits the internal representation or validation rules to change without changing the public interface.
Compare direct attribute access, getter and setter methods, and the property mechanism in Python.
- Direct attribute access is concise and appropriate when no validation or computed behavior is required. However, unrestricted assignment may allow invalid state.
- Getter and setter methods such as
get_age()andset_age()provide explicit control and validation, but they create a more verbose interface. - Properties provide controlled access while retaining normal attribute syntax such as
person.age.
Properties are generally preferred when an attribute requires validation, computation, read-only access, or backward-compatible changes to implementation. Direct access remains suitable for simple data attributes because Python does not require every field to have getters and setters. Effective encapsulation means exposing a stable, meaningful interface rather than hiding every attribute unnecessarily.
Design an encapsulated BankAccount class that validates deposits and withdrawals. Explain how the class preserves its invariants.
One possible implementation is:
class BankAccount:
def __init__(self, opening_balance=0):
if opening_balance < 0:
raise ValueError("Opening balance cannot be negative")
self.__balance = opening_balance
@property
def balance(self):
return self.__balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self.__balance += amount
def withdraw(self, amount):
if amount <= 0:
raise ValueError("Withdrawal must be positive")
if amount > self.__balance:
raise ValueError("Insufficient balance")
self.__balance -= amount
The class preserves the invariant that the balance must never be negative by:
- Validating the opening balance.
- Preventing direct ordinary assignment through a read-only
balanceproperty. - Rejecting non-positive transaction amounts.
- Rejecting withdrawals greater than the available balance.
- Centralizing all state changes in class methods.
Define inheritance and explain its advantages and possible disadvantages.
Inheritance is an OOP mechanism in which a child class derives attributes and methods from a parent class and may add or modify behavior.
Advantages include:
- Code reuse: Common behavior is written once in a parent class.
- Extensibility: Child classes can add specialized features.
- Polymorphism: Different child objects can be used through a common parent interface.
- Logical organization: It represents valid is-a relationships.
Possible disadvantages include:
- Tight coupling between parent and child classes.
- Fragile behavior when changes to a parent unexpectedly affect children.
- Deep hierarchies that become difficult to understand.
- Incorrect modeling when inheritance is used only to reuse code.
Inheritance should model a genuine is-a relationship. For has-a relationships, composition is usually more appropriate.
Describe single, multilevel, hierarchical, multiple, and hybrid inheritance with Python-oriented examples.
- Single inheritance: One child inherits from one parent, such as
class Dog(Animal). - Multilevel inheritance: A class inherits through a chain, such as
AnimaltoMammaltoDog. - Hierarchical inheritance: Multiple child classes inherit from one parent, such as
Dog(Animal)andCat(Animal). - Multiple inheritance: One child inherits from more than one parent, such as
class SmartPhone(Camera, Phone). - Hybrid inheritance: A program combines two or more inheritance patterns, often producing a diamond-shaped hierarchy.
Python supports all these forms. In multiple and hybrid inheritance, the method resolution order (MRO) determines where Python searches for methods. Such designs should use cooperative super() calls and compatible method signatures to avoid duplicated initialization and ambiguous behavior.
Explain method overriding in Python and show how it enables runtime polymorphism.
Method overriding occurs when a child class defines a method with the same name as a method inherited from its parent, providing specialized behavior.
Example:
class Animal:
def speak(self):
return "Unknown sound"
class Dog(Animal):
def speak(self):
return "Bark"
class Cat(Animal):
def speak(self):
return "Meow"
animals = [Dog(), Cat()]
for animal in animals:
print(animal.speak())
Although the loop uses the same call, animal.speak(), Python selects the implementation based on the actual object at runtime. This is runtime polymorphism or dynamic dispatch. The parent method can still be called from an override using super().speak() when its behavior must be retained.
What is the purpose of super() in Python inheritance? Explain its use in constructors and overridden methods.
super() returns a proxy that delegates method calls to the next class in the object's method resolution order.
Example:
class Person:
def __init__(self, name):
self.name = name
def describe(self):
return f"Person: {self.name}"
class Student(Person):
def __init__(self, name, roll_number):
super().__init__(name)
self.roll_number = roll_number
def describe(self):
return f"{super().describe()}, Roll: {self.roll_number}"
Benefits of super() include:
- Reusing parent initialization and behavior without naming the parent directly.
- Reducing duplicated code.
- Supporting refactoring more effectively than explicit parent-class calls.
- Enabling cooperative multiple inheritance by following the MRO.
Every participating class in a cooperative hierarchy should call super() consistently.
Explain method resolution order in Python. How does it resolve ambiguity in multiple inheritance?
Method resolution order (MRO) is the sequence in which Python searches classes for an attribute or method. Python uses C3 linearization to create a consistent order that respects subclass priority, the declared order of base classes, and existing parent-class orderings.
Example:
class A:
def show(self):
return "A"
class B(A):
pass
class C(A):
def show(self):
return "C"
class D(B, C):
pass
For D, the MRO is D, B, C, A, and object. Therefore, D().show() finds show() in C before reaching A.
The order can be inspected using D.mro() or D.__mro__. super() follows this complete order rather than merely calling a class's lexical parent, which is essential for cooperative multiple inheritance.
Describe the diamond problem in multiple inheritance and explain how Python handles it.
The diamond problem arises when two classes inherit from the same base class and another class inherits from both of them. This forms a diamond-shaped hierarchy.
Example structure:
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
Without a consistent lookup rule, Python might not know whether to search through B or C first, and common base behavior in A might be executed more than once.
Python handles this by:
- Computing an MRO using C3 linearization.
- Searching each class in a predictable sequence.
- Allowing
super()to advance to the next class in that sequence. - Visiting each class once during a properly cooperative call chain.
For D, the typical order is D, B, C, A, and object. Constructors should accept compatible arguments and use super() consistently.
Distinguish between method overriding and method overloading. How is overloading commonly simulated in Python?
Method overriding occurs across an inheritance hierarchy when a child replaces an inherited method with its own implementation. It directly supports runtime polymorphism.
Method overloading traditionally means defining multiple methods with the same name but different parameter lists in one class. Python does not support this form directly because a later definition with the same name replaces the earlier one.
Python commonly simulates overloading through:
- Default parameter values.
- Variable-length positional arguments using
*args. - Variable-length keyword arguments using
**kwargs. - Type inspection when truly necessary.
functools.singledispatchorsingledispatchmethodfor type-based dispatch.
Example:
class Calculator:
def add(self, *values):
return sum(values)
Thus, overriding chooses among implementations based on the object's class, while simulated overloading handles different argument patterns inside one callable interface.
Compare inheritance and composition. State when each technique should be preferred.
Inheritance creates an is-a relationship. A child receives behavior from a parent and may specialize it. For example, a Car may inherit from Vehicle if every car can correctly be treated as a vehicle.
Composition creates a has-a relationship. One object stores and delegates work to another object. For example, a Car has an Engine.
Comparison:
- Inheritance promotes reuse through a class hierarchy; composition promotes reuse through collaborating objects.
- Inheritance creates stronger coupling to a parent implementation; composition generally allows components to be replaced more easily.
- Inheritance naturally supports subtype polymorphism; composition supports flexible delegation.
- Deep inheritance hierarchies can become fragile; composition often keeps responsibilities more independent.
Prefer inheritance for a stable, genuine subtype relationship. Prefer composition when behavior must be assembled, exchanged, independently tested, or reused without claiming an is-a relationship.
Explain how encapsulation and inheritance interact. What risks arise when a subclass accesses a parent's internal state directly?
Encapsulation defines the public or protected interface through which subclasses should interact with parent behavior. Inheritance reuses that interface and extends it.
Direct dependence on a parent's internal state creates several risks:
- The subclass may violate invariants maintained by the parent.
- A change in the parent's internal representation may break the subclass.
- Parent and child become tightly coupled.
- Validation or logging performed by parent methods may be bypassed.
- Private names may be misunderstood because name mangling gives parent and child attributes different internal names.
A subclass should normally use public methods, properties, or deliberately protected extension points. Private double-underscore attributes are useful when the parent must avoid accidental naming conflicts, but they are not intended as a secure boundary. Well-designed base classes document what subclasses may override or access.
Develop a Python class hierarchy for employees that demonstrates inheritance, encapsulation, overriding, and polymorphism. Explain the design.
A suitable design is:
class Employee:
def __init__(self, name, base_pay):
if base_pay < 0:
raise ValueError("Base pay cannot be negative")
self.name = name
self.__base_pay = base_pay
@property
def base_pay(self):
return self.__base_pay
def calculate_pay(self):
return self.__base_pay
class Manager(Employee):
def __init__(self, name, base_pay, bonus):
super().__init__(name, base_pay)
if bonus < 0:
raise ValueError("Bonus cannot be negative")
self.bonus = bonus
def calculate_pay(self):
return super().calculate_pay() + self.bonus
class SalesEmployee(Employee):
def __init__(self, name, base_pay, commission):
super().__init__(name, base_pay)
if commission < 0:
raise ValueError("Commission cannot be negative")
self.commission = commission
def calculate_pay(self):
return super().calculate_pay() + self.commission
staff = [Manager("Asha", 50000, 10000), SalesEmployee("Ravi", 30000, 8000)]
total = sum(person.calculate_pay() for person in staff)
Design explanation:
Employeeencapsulates base pay and validates it.ManagerandSalesEmployeeinherit common state and behavior.super()reuses parent initialization and calculation.- Both child classes override
calculate_pay(). - The final expression demonstrates polymorphism because the same method call produces class-specific results.
Define object-oriented programming. Explain its major features in the context of Python.
Object-oriented programming (OOP) is a programming approach in which software is organized around objects that combine data and behavior.
Major OOP features are:
- Classes and objects: A class is a blueprint, while an object is an instance of that class.
- Encapsulation: Data and the methods operating on it are grouped together, and access to internal state is controlled.
- Inheritance: A new class can reuse and extend the attributes and methods of an existing class.
- Polymorphism: The same interface or method name can produce different behavior for different objects.
- Abstraction: Unnecessary implementation details are hidden while essential operations are exposed.
- Dynamic binding: Python determines the method to execute at runtime based on the actual object.
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →