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 Student class 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.

Frequently Asked Questions

What is the difference between a class and an object? A class is the template that describes what something has and can do. An object is one concrete thing built from that template. Student is the class; the variable holding Asha's data is an object. One class, many objects.
Why does every method need self? Because methods are shared by all objects of the class, Python passes in the specific object being worked on as the first argument. Without self, a method would have no way of knowing whose name or marks to read. The name self is convention, not a keyword.
What is the difference between a class attribute and an instance attribute? A class attribute is defined in the class body and shared by every object. An instance attribute is created inside __init__ with self and belongs to one object only. Use class attributes for constants, never for mutable values like lists.
Does Python support private variables? Not enforced. A leading underscore signals internal use by convention, and a double underscore triggers name mangling that makes access awkward but still possible. Python relies on convention rather than access control, which is a deliberate design choice.
Is OOP required for Class 12 Computer Science? Classes and objects appear in the syllabus, though the depth is limited compared with a university course. Understanding constructors, instance versus class attributes and basic inheritance covers the examinable material comfortably.