Unit 5: Classes and objects; Object oriented programming terminology - Subjective Questions
INT108 — Python Programming • Practice Questions with Detailed Answers
20 questions
Define a class in Python. Explain the general syntax for creating a class with a suitable example.
A class is a user-defined blueprint used to create objects. It groups related data, called attributes, and behavior, called methods, into a single unit.
General syntax:
class ClassName:
def __init__(self, parameters):
self.attribute = parameters
def method_name(self):
# method body
pass
Example:
class Student:
def __init__(self, name, roll_number):
self.name = name
self.roll_number = roll_number
def display(self):
print(self.name, self.roll_number)
Studentis the class name.__init__()initializes a newly created object.selfrefers to the current instance.nameandroll_numberare instance attributes.display()is an instance method.
What is an instance object? Describe how instance objects are created and initialized in Python.
An instance object is a concrete object created from a class. Every instance has its own identity and can maintain its own instance attribute values.
Example:
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
employee1 = Employee("Asha", 50000)
employee2 = Employee("Ravi", 60000)
When Employee("Asha", 50000) is evaluated:
- Python creates a new
Employeeobject. - The new object is passed to
__init__()asself. - The supplied arguments initialize
self.nameandself.salary. - A reference to the object is assigned to
employee1.
employee1 and employee2 are separate instances. Therefore, changing employee1.salary does not automatically change employee2.salary.
Explain the purpose of the self parameter in Python classes. What happens if it is omitted from an instance method?
self refers to the current instance on which an instance method is being executed. It allows the method to access or modify that object's attributes and invoke its other methods.
Example:
class Account:
def __init__(self, balance):
self.balance = balance
def deposit(self, amount):
self.balance += amount
account = Account(1000)
account.deposit(500)
The call account.deposit(500) is conceptually equivalent to Account.deposit(account, 500). Python passes account as the first argument automatically.
If the method is incorrectly defined as def deposit(amount):, the instance is still passed automatically, but it is assigned to amount. Supplying another argument then causes a TypeError because the number of arguments does not match.
self is a convention rather than a reserved keyword, but using this conventional name is strongly recommended.
Distinguish between instance attributes and class attributes in Python, using an example.
Instance attributes belong to individual objects, whereas class attributes belong to the class and are shared through the class by its instances.
class Product:
tax_rate = 0.18
def __init__(self, name, price):
self.name = name
self.price = price
p1 = Product("Keyboard", 2000)
p2 = Product("Mouse", 800)
Differences:
tax_rateis a class attribute and can be accessed asProduct.tax_rate,p1.tax_rate, orp2.tax_rate.nameandpriceare instance attributes, so each object stores its own values.- Changing
Product.tax_rateaffects instances that have not defined their own attribute with that name. - Assigning
p1.tax_rate = 0.10creates an instance attribute that shadows the class attribute only forp1.
Class attributes are suitable for values common to all instances, while instance attributes represent object-specific state.
Describe different ways of accessing, modifying, adding, and deleting object attributes in Python.
Object attributes are normally accessed with dot notation.
class Book:
def __init__(self, title):
self.title = title
book = Book("Python Basics")
Common operations:
- Access:
book.title - Modify:
book.title = "Advanced Python" - Add dynamically:
book.price = 450 - Delete:
del book.price
Python also provides built-in attribute functions:
getattr(book, "title")returns the attribute value.getattr(book, "author", "Unknown")returns a default if the attribute is absent.setattr(book, "price", 450)creates or updates an attribute.hasattr(book, "price")checks whether an attribute exists.delattr(book, "price")deletes an attribute.
Attempting to access a missing attribute directly generally raises AttributeError. The built-in functions are useful when an attribute name is determined dynamically.
Explain the role of the __init__() method. Is it technically responsible for creating an object?
__init__() is an initializer method that Python calls automatically after an instance has been created. Its main purpose is to establish the initial state of the object by assigning instance attributes and validating constructor arguments.
class Rectangle:
def __init__(self, length, width):
if length <= 0 or width <= 0:
raise ValueError("Dimensions must be positive")
self.length = length
self.width = width
def area(self):
return self.length * self.width
rectangle = Rectangle(5, 3)
Here, __init__() validates and stores the dimensions.
Technically, __init__() does not create the object. Object creation is performed by __new__(), after which Python calls __init__() to initialize the new instance. In ordinary class definitions, programmers usually override only __init__() because Python's inherited __new__() implementation handles creation.
Define inheritance. Demonstrate single inheritance in Python and explain its advantages.
Inheritance is an object-oriented mechanism through which a child class acquires attributes and methods from a parent class. The child may reuse, extend, or replace inherited behavior.
class Vehicle:
def __init__(self, brand):
self.brand = brand
def start(self):
return "Vehicle started"
class Car(Vehicle):
def drive(self):
return f"{self.brand} car is moving"
car = Car("Tata")
print(car.start())
print(car.drive())
Car inherits from Vehicle, so a Car object can use the inherited brand attribute and start() method. Since Car does not define its own initializer, it inherits Vehicle.__init__().
Advantages:
- Promotes code reuse.
- Reduces duplication.
- Supports specialization of general classes.
- Makes related classes easier to organize.
- Enables polymorphic treatment of parent and child objects.
Describe single, multilevel, hierarchical, and multiple inheritance in Python. Give a short example of each.
Python supports several inheritance structures.
1. Single inheritance: One child inherits from one parent.
class A:
pass
class B(A):
pass
2. Multilevel inheritance: A class inherits through a chain.
class A:
pass
class B(A):
pass
class C(B):
pass
Here, C indirectly inherits from A.
3. Hierarchical inheritance: Multiple children inherit from one parent.
class Vehicle:
pass
class Car(Vehicle):
pass
class Bike(Vehicle):
pass
4. Multiple inheritance: One child inherits from multiple parents.
class Camera:
def take_photo(self):
return "Photo taken"
class Phone:
def call(self):
return "Calling"
class SmartPhone(Camera, Phone):
pass
Python resolves inherited members using the class's method resolution order, which can be inspected with SmartPhone.mro().
What is method overriding? Explain how it enables runtime polymorphism with a suitable Python program.
Method overriding occurs when a child class defines a method with the same name as a method inherited from its parent. The child implementation is selected when that method is called on a child object.
class Shape:
def area(self):
raise NotImplementedError("Subclasses must implement area")
class Rectangle(Shape):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
shapes = [Rectangle(4, 5), Circle(3)]
for shape in shapes:
print(shape.area())
Although the loop uses the common shape.area() expression, Python selects the implementation according to the actual object's class at runtime. This is runtime polymorphism. It allows one interface to represent several class-specific behaviors.
Explain the use of super() in inheritance. How can a child class extend a parent class initializer and an overridden method?
super() returns a proxy that delegates attribute and method lookup to the next class in the method resolution order. It is commonly used to reuse parent behavior without naming the parent class directly.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def describe(self):
return f"{self.name}: {self.salary}"
class Manager(Employee):
def __init__(self, name, salary, department):
super().__init__(name, salary)
self.department = department
def describe(self):
basic = super().describe()
return f"{basic}, Department: {self.department}"
Manager.__init__() delegates initialization of name and salary to Employee.__init__() and then initializes department. Its describe() method also extends the inherited result.
Using super() is especially important in cooperative multiple inheritance because it follows the method resolution order rather than forcing a call to one explicitly named parent.
What is the Method Resolution Order (MRO)? Explain how Python resolves methods in multiple inheritance.
The Method Resolution Order (MRO) is the sequence in which Python searches classes for an attribute or method. It becomes especially important when several parent classes define a member with the same name.
class A:
def show(self):
return "A"
class B(A):
def show(self):
return "B"
class C(A):
def show(self):
return "C"
class D(B, C):
pass
print(D().show())
print(D.mro())
D().show() returns "B" because B appears before C in the MRO. The order is approximately D, B, C, A, and object.
Python computes the MRO using C3 linearization, which:
- Preserves the order in which base classes are declared.
- Ensures that a child is checked before its parents.
- Maintains a consistent ordering for inheritance hierarchies.
ClassName.mro() or ClassName.__mro__ can be used to inspect this order.
Define data hiding in Python. Explain the conventions for public, protected, and private attributes.
Data hiding limits direct access to an object's internal state so that the state can be controlled through methods or properties. Python relies mainly on naming conventions and name mangling rather than strict access-control keywords.
class Account:
def __init__(self, owner, balance):
self.owner = owner
self._branch_code = "B101"
self.__balance = balance
Attribute categories:
- Public:
ownercan be accessed freely asaccount.owner. - Protected by convention:
_branch_codeindicates that the attribute is intended for internal use or subclasses. Python still permits direct access. - Private through name mangling:
__balanceis internally transformed to a name similar to_Account__balance.
Name mangling reduces accidental access and naming conflicts, particularly in subclasses. However, it does not provide absolute security because the mangled name can still be accessed deliberately. Therefore, data hiding in Python is based partly on programmer discipline.
Explain name mangling for private attributes in Python. Does it provide complete data security?
When an attribute name begins with two underscores and does not end with two underscores, Python performs name mangling. It changes the stored name by including the class name.
class Vault:
def __init__(self, code):
self.__code = code
def verify(self, code):
return self.__code == code
vault = Vault("A123")
vault.__code normally raises AttributeError because the attribute is stored under a mangled name similar to vault._Vault__code.
Purpose of name mangling:
- Prevents accidental direct access.
- Avoids attribute-name collisions in subclasses.
- Signals that the member is an implementation detail.
It does not provide complete security. A programmer can still access vault._Vault__code deliberately. Python's privacy mechanism is intended to support encapsulation and prevent accidental misuse, not to protect sensitive data from hostile code.
Describe how getter and setter methods can be used to implement controlled access to hidden data.
Getter and setter methods provide an interface for reading and modifying hidden attributes. They allow validation and preserve class invariants.
class Student:
def __init__(self, marks):
self.__marks = 0
self.set_marks(marks)
def get_marks(self):
return self.__marks
def set_marks(self, marks):
if not 0 <= marks <= 100:
raise ValueError("Marks must be between 0 and 100")
self.__marks = marks
student = Student(75)
student.set_marks(82)
print(student.get_marks())
Benefits:
- The getter controls how data is exposed.
- The setter validates data before changing object state.
- Internal representation can change without changing calling code.
- Read-only or write-only behavior can be designed when required.
In modern Python, properties are often preferred because they provide the same control while preserving natural attribute-access syntax.
Explain the @property decorator. Write a class that uses a property to validate an attribute.
The @property decorator converts a method into a managed attribute. It enables getter, setter, and deleter logic while allowing callers to use ordinary dot notation.
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Temperature cannot be below absolute zero")
self._celsius = value
@property
def fahrenheit(self):
return self.celsius * 9 / 5 + 32
temperature = Temperature(25)
temperature.celsius = 30
print(temperature.fahrenheit)
The celsius getter returns the stored value, while its setter validates assignments. fahrenheit is a read-only computed property because no setter is defined. Properties support encapsulation without requiring calls such as get_celsius() and set_celsius().
What is function overloading? Explain why conventional compile-time function overloading is not directly supported in Python.
Function overloading traditionally means defining several functions with the same name but different parameter lists. Languages with compile-time overloading select an implementation based on the number or declared types of arguments.
Python does not directly support this form of overloading because a name in a scope normally refers to only one function object. A later definition replaces the earlier binding.
def calculate(a, b):
return a + b
def calculate(a, b, c):
return a + b + c
After these definitions, only the three-parameter version is bound to calculate. Calling calculate(2, 3) raises TypeError.
Python usually achieves similar flexibility through:
- Default parameter values.
- Variable-length arguments such as
*argsand**kwargs. - Type or value checks inside one function.
functools.singledispatchfor type-based dispatch on the first argument.- Differently named methods when operations have genuinely different meanings.
Demonstrate how default arguments and variable-length arguments can simulate function overloading in Python.
Python can simulate several overloaded call forms with one flexible function definition.
Using default arguments:
def area(length, width=None):
if width is None:
return length * length
return length * width
print(area(4))
print(area(4, 5))
Here, one argument calculates the area of a square, while two arguments calculate the area of a rectangle.
Using variable-length arguments:
def total(*values):
if not values:
return 0
return sum(values)
print(total())
print(total(10, 20))
print(total(10, 20, 30, 40))
*values collects positional arguments into a tuple. Similarly, **kwargs collects keyword arguments into a dictionary.
These techniques provide flexible call signatures, but they are not compile-time overloading. A single function receives the call and decides how to process its arguments at runtime.
Explain how functools.singledispatch provides type-based function overloading. State its main limitation.
functools.singledispatch creates a generic function whose implementation is selected according to the runtime type of its first argument.
from functools import singledispatch
@singledispatch
def describe(value):
return f"Object: {value}"
@describe.register
def _(value: int):
return f"Integer: {value}"
@describe.register
def _(value: list):
return f"List with {len(value)} items"
print(describe(10))
print(describe([1, 2, 3]))
print(describe("Python"))
The integer and list calls use their registered implementations, while the string call uses the generic implementation.
Main limitation: dispatch depends only on the type of the first argument, not on all arguments. It also differs from compile-time overloading because dispatch happens at runtime. For methods, Python provides functools.singledispatchmethod.
Compare method overriding and function overloading in Python.
Method overriding and function overloading both support polymorphic behavior, but they operate differently.
Method overriding:
- Requires inheritance.
- A child class replaces an inherited method implementation.
- The parent and child methods usually have compatible interfaces.
- Python selects the method according to the runtime class and MRO.
- It is directly supported by Python.
Function overloading:
- Traditionally uses multiple same-named functions with different signatures.
- Does not require inheritance.
- Compile-time overloading is not directly supported by normal Python definitions.
- Redefining a function name replaces the previous definition.
- Similar behavior can be implemented with defaults,
*args,**kwargs, orsingledispatch.
Example of overriding:
class Animal:
def sound(self):
return "Unknown"
class Dog(Animal):
def sound(self):
return "Bark"
Thus, overriding specializes inherited behavior, whereas overloading attempts to support several calling patterns under one function name.
Design a Python class hierarchy for a bank account system that demonstrates object creation, inheritance, method overriding, data hiding, and controlled attribute access.
A bank account hierarchy can place common behavior in a base class and specialize withdrawal rules in a child class.
class BankAccount:
def __init__(self, account_number, balance=0):
self.account_number = account_number
self.__balance = 0
self.deposit(balance)
@property
def balance(self):
return self.__balance
def deposit(self, amount):
if amount < 0:
raise ValueError("Deposit must not be negative")
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
class SavingsAccount(BankAccount):
def __init__(self, account_number, balance=0, minimum_balance=500):
super().__init__(account_number, balance)
self.minimum_balance = minimum_balance
def withdraw(self, amount):
if self.balance - amount < self.minimum_balance:
raise ValueError("Minimum balance would be violated")
super().withdraw(amount)
account = SavingsAccount("SA101", 5000)
account.deposit(1000)
account.withdraw(700)
print(account.balance)
Concepts demonstrated:
SavingsAccount(...)creates and initializes an instance object.SavingsAccountinherits fromBankAccount.withdraw()is overridden to enforce a minimum balance.__balanceis name-mangled to discourage direct access.- The read-only
balanceproperty provides controlled access. super()reuses base-class initialization and withdrawal logic.
Define a class in Python. Explain the general syntax for creating a class with a suitable example.
A class is a user-defined blueprint used to create objects. It groups related data, called attributes, and behavior, called methods, into a single unit.
General syntax:
class ClassName:
def __init__(self, parameters):
self.attribute = parameters
def method_name(self):
# method body
pass
Example:
class Student:
def __init__(self, name, roll_number):
self.name = name
self.roll_number = roll_number
def display(self):
print(self.name, self.roll_number)
Studentis the class name.__init__()initializes a newly created object.selfrefers to the current instance.nameandroll_numberare instance attributes.display()is an instance method.
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 →