InterviewQAs

Python Classes and Objects

Download as PDF
All questions in this page are included
Preparing…
Download PDF
PCA
Python Classes and Objects

Python classes and objects form the core of object-oriented programming in Python, enabling developers to model complex systems with encapsulation and reusability.

Beyond syntax, mastering classes involves understanding object lifecycle, memory management, and the interplay between class and instance attributes in real applications.

In modern software projects, classes are frequently used to implement design patterns such as singleton, factory, and observer, which provide structured and maintainable codebases.

Question 01

Explain the difference between class attributes and instance attributes in Python.

EASY

Class attributes are shared across all instances of a class, while instance attributes are specific to each object. Changing a class attribute affects all instances that have not overridden it.

For example, if you define a `vehicle_count` at the class level, all vehicle instances will reference the same value unless an instance explicitly overrides it.

Using instance attributes allows objects to maintain independent state. In practice, careful distinction avoids accidental shared state, which is especially important in web applications or multithreaded environments.

Question 02

Which of the following are valid ways to create a new object in Python?

MEDIUM
  • A Using the class name followed by parentheses, e.g., `obj = MyClass()`
  • B Calling the `__init__` method directly, e.g., `obj = MyClass.__init__()`
  • C Using a factory function that returns a new instance of the class
  • D Cloning an existing object using `copy.deepcopy()`

The standard approach is to instantiate a class via its constructor. Calling `__init__` directly does not create a new object and will raise errors if used improperly.

Factory functions provide flexibility to return different subclasses or preconfigured instances. Deep copying allows creating a completely independent object with the same state, useful in simulation or undo/redo logic.

Question 03

Write a Python class to represent a bank account with deposit and withdrawal methods, ensuring balance never goes negative.

EASY

This class encapsulates account state with proper validation to prevent negative balances, reflecting real-world banking rules.

Deposit and withdrawal methods include guards against invalid operations. This pattern demonstrates good practice for object state integrity and encapsulation.

// Python
class BankAccount:
    def __init__(self, account_number, balance=0):
        self.account_number = account_number
        self.balance = balance

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            return True
        return False

    def withdraw(self, amount):
        if 0 < amount <= self.balance:
            self.balance -= amount
            return True
        return False

# Example usage
account = BankAccount('12345', 1000)
account.deposit(500)
account.withdraw(300)
print(account.balance)  # Output: 1200
Question 04

How does Python handle method resolution order (MRO) when using multiple inheritance?

MEDIUM

Python uses the C3 linearization algorithm to determine the method resolution order in multiple inheritance scenarios. This ensures a consistent and predictable lookup path for methods and attributes.

When a method is called on an object, Python searches the instance?s class first, then follows the MRO sequence, which respects both the order of base classes and inheritance hierarchies.

Understanding MRO is crucial when designing complex class hierarchies, as it helps prevent ambiguity and ensures that overridden methods behave consistently across subclasses.

Question 05

Which of the following statements about Python class methods are correct?

HARD
  • A A class method can access and modify class attributes but not instance attributes directly
  • B Class methods are bound to the class and receive the class as the first argument
  • C Class methods cannot be called on an instance of the class
  • D Using `@classmethod` allows alternative constructors

Class methods operate on the class itself rather than individual instances, which makes them ideal for operations affecting all objects or for alternative constructors.

They are callable on both the class and instances, unlike static methods which do not receive class or instance references.

Question 06

Demonstrate how to implement a singleton pattern in Python using a class decorator.

MEDIUM

The decorator maintains a dictionary of class instances, ensuring only one instance exists for any decorated class.

Singletons are often used for configuration, logging, or connection pooling in applications where a single shared resource is required.

// Python
def singleton(cls):
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper

@singleton
class ConfigManager:
    def __init__(self):
        self.settings = {}

# Usage
cfg1 = ConfigManager()
cfg2 = ConfigManager()
print(cfg1 is cfg2)  # Output: True
Question 07

Explain the difference between shallow and deep copies of objects in Python and when to use each in class design.

HARD

A shallow copy creates a new object but references the same nested objects as the original. Modifying nested objects affects both copies.

A deep copy recursively duplicates all nested objects, producing an entirely independent object tree.

In class design, shallow copies are suitable for objects with immutable nested structures, while deep copies are essential when objects contain mutable nested data and independent manipulation is required.

Question 08

Which of the following statements about Python properties are true?

MEDIUM
  • A Properties can be used to implement getter and setter methods without changing the interface
  • B Using `@property` allows computed attributes to appear as normal instance variables
  • C Property decorators can only be applied to class methods, not instance methods
  • D Properties can help encapsulate validation logic

Properties allow you to add logic when getting or setting attributes without changing how the attribute is accessed, keeping API consistency.

They are ideal for validation, lazy computation, or read-only attributes, improving maintainability of real-world code.

Question 09

Create a Python class that dynamically adds attributes from a dictionary at initialization and prevents adding new attributes afterwards.

HARD

The class uses `__slots__` to restrict dynamic attribute creation after initialization, while allowing dynamic attributes from a dictionary during construction.

This pattern is practical in systems where object structure must be fixed after instantiation for memory efficiency or API stability.

// Python
class FrozenDynamic:
    __slots__ = []  # Placeholder to prevent arbitrary attribute creation

    def __init__(self, attr_dict):
        for key, value in attr_dict.items():
            object.__setattr__(self, key, value)
        object.__setattr__(self, '__slots__', list(attr_dict.keys()))

# Usage
data = {'name': 'Vijay', 'role': 'Engineer'}
obj = FrozenDynamic(data)
print(obj.name)  # Output: Vijay
# obj.age = 30  # This would raise AttributeError
Question 10

Implement a Python class with a class-level cache to avoid recomputing expensive calculations for repeated inputs.

MEDIUM

The class-level `_cache` dictionary stores previously computed results, preventing redundant calculations for the same input.

This approach is useful in data processing, analytics, or simulations where certain computations are repeated frequently and caching improves performance.

// Python
class ExpensiveComputation:
    _cache = {}

    @classmethod
    def compute(cls, x):
        if x in cls._cache:
            return cls._cache[x]
        # Simulate an expensive computation
        result = x ** 2 + 42
        cls._cache[x] = result
        return result

# Usage
print(ExpensiveComputation.compute(5))  # Computes and caches
print(ExpensiveComputation.compute(5))  # Returns cached value
Question 11

When would you choose composition over inheritance in Python class design?

MEDIUM

Composition is often a better choice when you want to build functionality by combining smaller, independent components rather than creating a deep inheritance hierarchy. It reduces coupling and makes classes easier to modify without affecting unrelated behavior.

For example, in an e-commerce system, an Order class may contain PaymentProcessor, TaxCalculator, and NotificationService objects. These components can be replaced independently without changing the Order class itself.

Inheritance works well for true 'is-a' relationships, but many real-world designs evolve over time. Composition tends to provide greater flexibility, easier testing, and lower maintenance costs in large applications.

Question 12

Which statements about the `self` parameter in Python are correct?

MEDIUM
  • A It refers to the current instance of the class
  • B It is automatically passed when an instance method is called
  • C It is a reserved keyword and cannot be renamed
  • D It provides access to instance attributes and methods

The `self` parameter represents the current object instance. Python automatically supplies it when an instance method is invoked.

Although `self` is a strong convention, it is not a reserved keyword. Using a different name is technically possible but strongly discouraged because it reduces readability.

Question 13

Create a class that tracks how many objects have been created.

MEDIUM

The class attribute `total_employees` is shared across all instances and is incremented whenever a new object is created.

This pattern is commonly used for monitoring active objects, generating statistics, or maintaining lightweight application metrics.

// Python
class Employee:
    total_employees = 0

    def __init__(self, name):
        self.name = name
        Employee.total_employees += 1

    @classmethod
    def get_count(cls):
        return cls.total_employees

emp1 = Employee('John')
emp2 = Employee('Sara')
print(Employee.get_count())
Question 14

What problems can arise from mutable class attributes and how can they be avoided?

HARD

Mutable class attributes such as lists or dictionaries are shared among all instances. A modification made by one instance becomes visible to every other instance that references the same attribute.

This often leads to difficult-to-diagnose bugs. For example, if a class-level list stores user preferences, one user's changes could unexpectedly appear for another user.

The safest approach is to initialize mutable objects inside `__init__` so that each instance receives its own independent copy. Class attributes should generally be reserved for truly shared state.

Question 15

Which special methods can be used to customize object behavior?

HARD
  • A `__str__` for string representation
  • B `__len__` for length calculation
  • C `__getitem__` for index access
  • D `__create__` for object construction

Python's data model allows classes to integrate naturally with built-in language features through special methods.

`__str__`, `__len__`, and `__getitem__` are commonly implemented to make custom objects behave like native Python types. There is no special method named `__create__`.

Question 16

Implement a class with a read-only property representing an employee ID.

MEDIUM

The property exposes the employee ID while preventing direct modification through a setter.

Read-only properties are useful when identifiers, creation timestamps, or audit fields should remain immutable after object creation.

// Python
class Employee:
    def __init__(self, employee_id):
        self._employee_id = employee_id

    @property
    def employee_id(self):
        return self._employee_id

emp = Employee('EMP001')
print(emp.employee_id)
Question 17

Create a class that supports sorting based on a custom attribute using operator overloading.

HARD

The `__lt__` method defines how objects should be compared when sorting operations are performed.

Custom comparison logic is commonly used in inventory systems, ranking engines, pricing applications, and recommendation systems.

// Python
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __lt__(self, other):
        return self.price < other.price

products = [
    Product('Laptop', 1200),
    Product('Mouse', 30),
    Product('Monitor', 400)
]

for product in sorted(products):
    print(product.name, product.price)
Question 18

Which statement about constructors in Python is correct?

EASY
  • A `__init__` initializes a newly created object
  • B `__init__` creates the object in memory
  • C A class can have only one constructor overload
  • D `__init__` can accept parameters

`__init__` is responsible for object initialization, not object creation. Object creation occurs before `__init__` executes.

Python does not support traditional constructor overloading, but optional arguments and variable-length parameters can achieve similar behavior.

Question 19

Why would you implement `__repr__` in a production application?

MEDIUM

The `__repr__` method provides a detailed and unambiguous representation of an object. It is particularly useful during debugging, logging, and troubleshooting.

When investigating production issues, developers often inspect object contents through logs. A well-designed `__repr__` can reveal important state information without requiring additional debugging code.

For domain objects such as orders, invoices, or transactions, a meaningful representation significantly improves observability and reduces investigation time.

Question 20

Implement a lazy-loading class that retrieves data only when it is first accessed.

HARD

The profile data is loaded only when the property is accessed for the first time. Subsequent accesses return the cached value.

Lazy loading is widely used in ORMs, API clients, and large-scale systems where retrieving data is expensive and should be deferred until actually needed.

// Python
class UserProfile:
    def __init__(self, user_id):
        self.user_id = user_id
        self._profile = None

    @property
    def profile(self):
        if self._profile is None:
            print('Loading profile from database...')
            self._profile = {
                'user_id': self.user_id,
                'name': 'John Doe'
            }
        return self._profile

user = UserProfile(101)
print(user.profile)
print(user.profile)
Question 21

How can Python metaclasses be used to enforce coding standards or class constraints?

MEDIUM

Metaclasses allow you to control class creation, enabling checks or modifications to classes before they are instantiated. This can enforce coding standards, such as ensuring certain methods exist or attributes are present.

For example, a metaclass could verify that every data model class has a `to_dict()` method or that all class attributes follow a naming convention.

In real-world applications, metaclasses are commonly used in frameworks like Django or SQLAlchemy to provide consistent behavior, validate schemas, and reduce repetitive boilerplate code.

Question 22

Which of the following statements about `__slots__` in Python are correct?

MEDIUM
  • A `__slots__` reduces memory usage by preventing the creation of `__dict__` for instances
  • B `__slots__` allows dynamic addition of new attributes
  • C `__slots__` can improve attribute access speed
  • D `__slots__` can be inherited by subclasses

`__slots__` is useful when creating large numbers of objects with fixed attributes, improving memory and performance.

However, `__slots__` restricts dynamic attribute addition. Subclasses can inherit and extend slots, but care is needed to avoid conflicts.

Question 23

Write a Python class that logs every method call automatically.

EASY

By overriding `__getattribute__`, the class intercepts method calls and prints logs before executing them.

This is a practical pattern for debugging, auditing, or monitoring method usage without modifying the original method code.

// Python
class Logger:
    def __getattribute__(self, name):
        attr = object.__getattribute__(self, name)
        if callable(attr) and not name.startswith('__'):
            def newfunc(*args, **kwargs):
                print(f'Calling {name} with {args} {kwargs}')
                return attr(*args, **kwargs)
            return newfunc
        return attr

    def greet(self, name):
        print(f'Hello, {name}!')

log = Logger()
log.greet('Vijay')
Question 24

Explain Python's difference between shallow copy, deep copy, and copy-on-write concepts in object handling.

MEDIUM

A shallow copy duplicates the outer object but keeps references to nested objects, so changes to nested structures affect both copies.

A deep copy recursively duplicates nested objects, creating fully independent objects.

Copy-on-write is not a built-in Python behavior but can be implemented to delay copying until an object is modified. This is useful for performance optimization when creating multiple copies of large immutable or semi-mutable structures.

Question 25

Which statements about Python decorators applied to classes are true?

HARD
  • A Decorators can modify or replace a class entirely
  • B Decorators only work on methods, not classes
  • C Decorators are executed at class definition time
  • D Multiple decorators can be applied to a single class

Class decorators wrap the class object itself, allowing modification, replacement, or enhancement.

They run when the class is defined, not when instances are created, and multiple decorators are applied in order from bottom to top.

Question 26

Create a Python class representing a cache with automatic expiration of items after a given time.

MEDIUM

The cache stores values with timestamps and removes entries once they expire. This pattern is common in API response caching, temporary session storage, and memory optimization.

By encapsulating logic in a class, the behavior remains reusable and testable.

// Python
import time

class ExpiringCache:
    def __init__(self, ttl_seconds):
        self.ttl = ttl_seconds
        self.store = {}

    def set(self, key, value):
        self.store[key] = (value, time.time())

    def get(self, key):
        if key in self.store:
            value, timestamp = self.store[key]
            if time.time() - timestamp < self.ttl:
                return value
            else:
                del self.store[key]
        return None

cache = ExpiringCache(2)
cache.set('x', 100)
print(cache.get('x'))
time.sleep(3)
print(cache.get('x'))
Question 27

Implement a Python class using `__call__` so that instances can be invoked like functions.

HARD

The `__call__` method allows instances to behave like functions, making APIs more flexible and objects usable as callable entities.

This is useful for creating configurable function-like objects, callbacks, or strategies where behavior can be parameterized at instantiation.

// Python
class Multiplier:
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, x):
        return x * self.factor

m = Multiplier(5)
print(m(10))  # Output: 50
Question 28

What is the purpose of `__del__` method in Python classes and why should it be used cautiously?

EASY

The `__del__` method is a destructor called when an object is about to be destroyed, allowing cleanup of resources such as file handles or network connections.

However, Python?s garbage collector does not guarantee when `__del__` will be invoked, and objects participating in reference cycles may not trigger it at all.

For this reason, explicit context managers or `try/finally` blocks are generally preferred over relying solely on `__del__` for resource management.

Question 29

Which statements about static methods in Python are correct?

MEDIUM
  • A Static methods are bound to the class, not the instance
  • B Static methods cannot access class or instance attributes
  • C Static methods require the `@staticmethod` decorator
  • D Static methods automatically receive the instance as the first parameter

Static methods provide utility functions within the class namespace without accessing instance or class state.

Unlike class or instance methods, they do not receive `self` or `cls` automatically and are primarily used for logically related operations.

Question 30

Create a Python class that implements an observer pattern allowing multiple listeners to be notified of changes.

MEDIUM

The class maintains a list of listeners and notifies them when an event occurs. This decouples event producers from consumers.

Observer patterns are widely used in GUI frameworks, event-driven systems, and reactive programming to implement callbacks and notifications.

// Python
class Observable:
    def __init__(self):
        self._observers = []

    def register(self, observer):
        self._observers.append(observer)

    def notify(self, message):
        for obs in self._observers:
            obs.update(message)

class Listener:
    def __init__(self, name):
        self.name = name

    def update(self, message):
        print(f'{self.name} received: {message}')

obs = Observable()
l1 = Listener('A')
l2 = Listener('B')
obs.register(l1)
obs.register(l2)
obs.notify('Event occurred!')