What you'll learn
Quick Answer
A class is a template; an object is one thing made from it. Instance attributes go in __init__ and belong to one object. Class attributes sit outside it and are shared by every object — which is useful for constants and a bug waiting to happen for lists.
A class is a template, an object is a thing
Suppose you are storing students. Without classes you end up with parallel lists, or dictionaries you have to remember the shape of. A class lets you say once what a student is, and then make as many as you like.
class Student:
school = "Priodemy" # class attribute, shared by all
def __init__(self, name, marks):
self.name = name # instance attributes, per object
self.marks = marks
def grade(self):
if self.marks >= 90: return "A"
if self.marks >= 75: return "B"
return "C"
def __str__(self):
return f"{self.name} ({self.marks})"
a = Student("Asha", 91)
b = Student("Ravi", 68)
print(a, "->", a.grade()) # Asha (91) -> A
print(b, "->", b.grade()) # Ravi (68) -> C
Three things are doing the work here. __init__ runs automatically when you write Student(...) and is where you attach data to the object. self is that object — it is not a keyword, just a conventional name for the first parameter Python passes in. And __str__ decides what print() shows, which is why the output reads Asha (91) rather than an unhelpful <__main__.Student object at 0x...>.
Class attributes: shared on purpose, shared by accident
school sits outside __init__, so there is exactly one copy shared by every Student:
print(a.school, b.school) # Priodemy Priodemy
That is fine for a constant. It becomes a bug the moment the shared thing is mutable:
class BadClass:
subjects = [] # ONE list, shared by every instance
class GoodClass:
def __init__(self):
self.subjects = [] # a new list per instance
x, y = BadClass(), BadClass()
x.subjects.append("Physics")
print(y.subjects) # ['Physics'] <-- y was never touched
p, q = GoodClass(), GoodClass()
p.subjects.append("Physics")
print(q.subjects) # []
Appending to x changed y, because they were never separate lists. This is the same underlying trap as a mutable default argument, and it is one of the most common sources of "impossible" bugs in beginner code. The rule is simple: if it is mutable and it should be per-object, create it inside __init__.
Inheritance: reuse, then change one thing
Inheritance lets a new class take everything an existing class has and change only what differs.
class TopperStudent(Student):
def grade(self): # override
return "A+" if self.marks >= 95 else super().grade()
t = TopperStudent("Meera", 96)
print(t, "->", t.grade()) # Meera (96) -> A+
print(isinstance(t, Student)) # True
TopperStudent never defines __init__ or __str__ — it inherits both. It redefines grade(), and super().grade() calls the original when the new rule does not apply, so the A/B/C logic is not duplicated.
That isinstance result is the point of inheritance, not a side effect: a TopperStudent is a Student, so anything written to work with Students works with it unchanged.
The four pillars, without the jargon
Interviewers ask for these by name, so it is worth being able to give a one-line answer plus an example from your own code.
- Encapsulation — keeping data and the functions that work on it in one place. The
Studentclass above is encapsulation. - Inheritance — a new class reusing an existing one.
TopperStudent. - Polymorphism — the same call doing the right thing for different types. Calling
.grade()on a list of mixed Students and TopperStudents works without checking which is which. - Abstraction — exposing what something does and hiding how. You call
.grade()without caring about the thresholds inside it.
Python has no truly private attributes. A single underscore (_marks) is a convention meaning "internal, do not touch"; a double underscore triggers name mangling, which discourages access but does not prevent it. If an interviewer asks whether Python supports encapsulation, that nuance is the answer they are looking for.
When not to use a class
Beginners who have just learned OOP tend to wrap everything in a class, including things that should be one function. A class earns its place when you have data plus behaviour that belongs together, and especially when you will have many instances of it.
If your class has one method and no state, it should be a function. If it has state but no behaviour, a dictionary or a dataclass is usually clearer. Being able to say that in an interview signals real judgement rather than memorised definitions.
For Class 12 students, this topic is also directly examinable — see our free Class 12 Computer Science chapters for the syllabus version and practice questions.
