Python OOP, the Python way
Know how attributes are looked up (instance, class, MRO), how to extend parents with super(), and how Python prefers conventions, properties and duck typing over strict access control.
- ClassesInstance vs class attributes and dunder methods.
- Inheritancesuper(), MRO and abstract base classes.
- DesignEncapsulation, duck typing and composition.
Python supports full object-oriented programming, but in its own relaxed style: no access modifiers, duck typing instead of interfaces, and multiple inheritance with a clear lookup order. Interviewers check that you can design clean classes and explain how Python finds attributes and methods.
1. Class attributes vs instance attributes — what is the trap?
Class attributes are shared by all instances; instance attributes belong to one object. The trap: a mutable class attribute such as a list is shared, so one object's changes appear in every other object. Initialise per-object state in __init__.
class Team:
members = [] # shared by ALL teams — bug
def __init__(self, name):
self.name = name
self.players = [] # one list per team — correct
a, b = Team("A"), Team("B")
a.members.append("Asha")
print(b.members) # ['Asha'] — surprise!2. How does inheritance work and what does super() do?
A subclass inherits attributes and methods and can override them. super() returns a proxy to the next class in the method resolution order, so you can extend a parent's behaviour instead of copying it. Always call super().__init__() when the parent needs initialising.
class Account:
def __init__(self, owner, balance=0):
self.owner, self.balance = owner, balance
def withdraw(self, amount):
if amount > self.balance:
raise ValueError("Insufficient balance")
self.balance -= amount
class SavingsAccount(Account):
def __init__(self, owner, balance=0, rate=0.04):
super().__init__(owner, balance)
self.rate = rate
def add_interest(self):
self.balance += self.balance * self.rate3. What is the MRO and how does Python resolve multiple inheritance?
The Method Resolution Order is the sequence of classes Python searches for an attribute. It is computed with the C3 linearisation algorithm: children before parents, and the left-to-right order of bases is kept. This solves the diamond problem — the shared base appears once, at the end. Inspect it with ClassName.__mro__.
class A:
def hello(self): return "A"
class B(A):
def hello(self): return "B"
class C(A):
def hello(self): return "C"
class D(B, C):
pass
print(D().hello()) # B
print([k.__name__ for k in D.__mro__]) # ['D', 'B', 'C', 'A', 'object']4. How do you achieve encapsulation in Python?
Python has no private keyword; it relies on convention. A single underscore (_balance) means “internal, do not touch”. A double underscore (__pin) triggers name mangling to _ClassName__pin, which prevents accidental clashes in subclasses. Expose controlled access with @property and validation in setters.
class Wallet:
def __init__(self):
self._balance = 0
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("Balance cannot be negative")
self._balance = value5. What are abstract base classes?
An abstract base class (from abc) defines methods subclasses must implement. You cannot instantiate it, and a subclass that forgets an abstract method fails at creation time — catching mistakes early. Use ABCs for plugin-style designs such as payment gateways or storage backends.
from abc import ABC, abstractmethod
class PaymentGateway(ABC):
@abstractmethod
def charge(self, amount: float) -> str: ...
class Razorpay(PaymentGateway):
def charge(self, amount):
return f"razorpay-txn-{amount}"
# PaymentGateway() -> TypeError: Can't instantiate abstract class6. What is duck typing and how does it relate to polymorphism?
“If it walks like a duck and quacks like a duck, it is a duck.” Python cares whether an object has the methods you call, not its declared type. Any object with a read() method can be used where a file is expected. typing.Protocol lets type checkers verify this structural typing.
from typing import Protocol
class Notifier(Protocol):
def send(self, to: str, text: str) -> None: ...
class Email:
def send(self, to, text): print(f"email {to}: {text}")
class Sms:
def send(self, to, text): print(f"sms {to}: {text}")
def alert(n: Notifier):
n.send("Asha", "Interview at 10 AM")
for channel in (Email(), Sms()):
alert(channel) # polymorphism without a shared base class7. Composition vs inheritance — which do you prefer?
Inheritance models an “is-a” relationship; composition models “has-a” by holding other objects. Prefer composition: it keeps classes small, avoids deep fragile hierarchies and lets you swap parts at runtime. Use inheritance for genuine specialisation or framework hooks.
class Engine:
def start(self): return "engine on"
class Car:
def __init__(self, engine: Engine):
self.engine = engine # has-a
def drive(self):
return self.engine.start() + ", driving"
print(Car(Engine()).drive())8. What do __str__ and __repr__ do?
__repr__ is the unambiguous developer view, ideally valid code to recreate the object — shown in the console, logs and debuggers. __str__ is the friendly user view used by print(). Always implement __repr__; add __str__ when the user-facing text differs.
class Order:
def __init__(self, id, total):
self.id, self.total = id, total
def __repr__(self):
return f"Order(id={self.id!r}, total={self.total!r})"
def __str__(self):
return f"Order #{self.id} — ₹{self.total}"
o = Order(1042, 899)
print(o) # Order #1042 — ₹899
print([o]) # [Order(id=1042, total=899)]Remember
Key points
- Mutable class attributes are shared
Create per-object state in __init__.
- super() follows the MRO
Not simply “the parent class”.
- Conventions over keywords
_internal, __mangled and @property.
- Prefer composition
Small classes you can combine and swap.
Interview drill
Follow-up questions
What the interviewer asks after your first answer. Turn on practice mode, answer each one aloud, then reveal.
-
1 What is the difference between __new__ and __init__? Advanced
__new__creates and returns the instance (used for immutable types and singletons);__init__initialises the already-created instance. -
2 What is a metaclass? Advanced
The class of a class — it controls how classes are created.
typeis the default. Frameworks such as Django ORM use them; in application code, class decorators or__init_subclass__are usually simpler. -
3 How do you implement a singleton in Python? Intermediate
The simplest way is a module-level instance (modules are imported once). Alternatively override
__new__to return a cached instance. -
4 What is a mixin? Intermediate
A small class that adds one capability (such as
JsonMixin.to_json()) to be combined through multiple inheritance; it is not meant to stand alone. -
5 How do you make objects comparable and sortable? Intermediate
Implement
__eq__and__lt__and add@functools.total_ordering, or use@dataclass(order=True). -
6 What does isinstance() check compared with type()? Basic
isinstance(obj, Cls)respects inheritance (a subclass instance passes);type(obj) is Clsmatches only the exact class. Prefer isinstance. -
7 What are class methods used for in real code? Basic
Alternative constructors such as
User.from_dict(data)orDate.today(); they receive the class, so subclasses get the right type.