CS Pathfinder Logo CS Pathfinder

Unit 5 · Advanced Topics

Classes and Objects

Understand how to define classes, create objects, use the __init__ constructor, and work with instance and class variables in Python. The foundation of all OOP programming.

Table of Contents

What is a Class?

University Definition

A class is a user-defined blueprint or template that defines the attributes (data) and methods (functions) that its objects will have. It is a logical entity that does not occupy memory until an object is created from it.

Think of a class as an architect's blueprint for a house. The blueprint itself is not a house — it describes what a house will look like. Similarly, a class describes what objects of that type will contain, but the class itself is not an object.

What is an Object?

University Definition

An object is an instance of a class. It is a runtime entity that occupies memory and contains actual values for the attributes defined in the class. Objects are created through a process called instantiation.

Class vs Object
================

Class:    Student           (template/blueprint)
Object:   Student("Rahul")  (actual instance)

Class:    Car               (template/blueprint)
Object:   Car("Toyota")     (actual instance)

Class does NOT occupy memory
Object OCCUPIES memory

Creating a Class

# Basic class definition
class Student:
    pass  # Empty class

# Class with attributes
class Student:
    name = "Rahul"      # Class attribute
    age = 20            # Class attribute

# Creating objects
s1 = Student()
s2 = Student()

# Accessing attributes
print(s1.name)   # Rahul
print(s2.name)   # Rahul
print(s1.age)    # 20

# Modifying attributes
s1.name = "Priya"
print(s1.name)   # Priya
print(s2.name)   # Rahul (unchanged)

# Class with methods
class Dog:
    def bark(self):
        print("Woof! Woof!")

    def sit(self):
        print("The dog is sitting")

# Creating object and calling methods
my_dog = Dog()
my_dog.bark()   # Woof! Woof!
my_dog.sit()    # The dog is sitting

__init__ Constructor

University Definition

The __init__ method is a special method (constructor) in Python that is automatically called when a new object is created from a class. It initializes the object's attributes with the values passed during creation.

class Student:
    def __init__(self, name, age, grade):
        self.name = name      # Instance variable
        self.age = age        # Instance variable
        self.grade = grade    # Instance variable

    def display(self):
        print(f"Name: {self.name}")
        print(f"Age: {self.age}")
        print(f"Grade: {self.grade}")

# __init__ is called automatically
s1 = Student("Rahul", 20, "A")
s2 = Student("Priya", 21, "B+")

s1.display()
print("---")
s2.display()

# Accessing attributes directly
print(f"{s1.name} is {s1.age} years old")
print(f"{s2.name} is in grade {s2.grade}")

Output:

Name: Rahul
Age: 20
Grade: self.grade
---
Name: Priya
Age: 21
Grade: self.grade
Rahul is 20 years old
Priya is in grade B+

self Parameter

University Definition

The self parameter refers to the current instance of the class. It is used to access variables and methods belonging to the object. It must be the first parameter in every instance method but is never passed explicitly when calling the method.

class Person:
    def __init__(self, name):
        self.name = name      # self.name = attribute of the object

    def greet(self):
        # self refers to the specific object calling this method
        print(f"Hello, my name is {self.name}")
        print(f"I am {id(self)} years old in memory")

p1 = Person("Alice")
p2 = Person("Bob")

p1.greet()
print("---")
p2.greet()

# self is the object itself
print(f"p1 is: {p1}")
print(f"p2 is: {p2}")
# p1 and p2 are different objects in memory

Output:

Hello, my name is Alice
I am 140234567890 years old in memory
---
Hello, my name is Bob
I am 140234568234 years old in memory
p1 is: <__main__.Person object at 0x...>
p2 is: <__main__.Person object at 0x...>

University Exam Tip

self is not a keyword — it is a convention. You can name it anything, but using self is the standard Python convention. When you call obj.method(arg), Python automatically passes obj as the first argument (self).

Instance Variables vs Class Variables

class Student:
    # CLASS VARIABLES - shared by all objects
    school = "ABC University"
    total_students = 0

    def __init__(self, name, age):
        # INSTANCE VARIABLES - unique to each object
        self.name = name
        self.age = age
        Student.total_students += 1

    def display(self):
        print(f"{self.name}, Age: {self.age}, School: {Student.school}")

# Creating objects
s1 = Student("Rahul", 20)
s2 = Student("Priya", 21)
s3 = Student("Amit", 22)

# Instance variables - different for each object
print(s1.name)   # Rahul
print(s2.name)   # Priya
print(s3.name)   # Amit

# Class variable - same for all objects
print(s1.school)           # ABC University
print(Student.school)      # ABC University
print(Student.total_students)  # 3

# Modifying class variable affects all objects
Student.school = "XYZ University"
print(s1.school)   # XYZ University
print(s2.school)   # XYZ University

# Modifying instance variable affects only that object
s1.name = "Rahul Sharma"
print(s1.name)     # Rahul Sharma
print(s2.name)     # Priya (unchanged)

Output:

Rahul
Priya
Amit
ABC University
ABC University
3
XYZ University
XYZ University
Rahul Sharma
Priya
Feature Instance Variable Class Variable
Defined in__init__ using self.varInside class, outside methods
SharedUnique to each objectShared by all objects
MemorySeparate for each objectSingle copy for all
Accessobject.varClassName.var or object.var

__str__ and __repr__

class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade

    def __str__(self):
        """Human-readable string (for print, str())"""
        return f"Student({self.name}, Grade: {self.grade})"

    def __repr__(self):
        """Developer representation (for debugging)"""
        return f"Student('{self.name}', '{self.grade}')"

s = Student("Rahul", "A")

# __str__ is called by print() and str()
print(s)            # Student(Rahul, Grade: A)
print(str(s))       # Student(Rahul, Grade: A)

# __repr__ is called in the interpreter/debugger
print(repr(s))      # Student('Rahul', 'A')

# In a list, __repr__ is used
print([s, Student("Priya", "B+")])
# [Student('Rahul', 'A'), Student('Priya', 'B+')]

Output:

Student(Rahul, Grade: A)
Student(Rahul, Grade: A)
Student('Rahul', 'A')
[Student('Rahul', 'A'), Student('Priya', 'B+')]

Multiple Objects Example

class BankAccount:
    bank_name = "State Bank of India"

    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance
        self transactions = []

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            self.transactions.append(f"+{amount}")
            print(f"Deposited {amount}. Balance: {self.balance}")

    def withdraw(self, amount):
        if 0 < amount <= self.balance:
            self.balance -= amount
            self.transactions.append(f"-{amount}")
            print(f"Withdrawn {amount}. Balance: {self.balance}")
        else:
            print("Insufficient funds!")

    def get_statement(self):
        print(f"--- Statement for {self.owner} ---")
        print(f"Bank: {BankAccount.bank_name}")
        print(f"Transactions: {', '.join(self.transactions)}")
        print(f"Final Balance: {self.balance}")

# Creating multiple objects
acc1 = BankAccount("Rahul", 5000)
acc2 = BankAccount("Priya", 10000)

acc1.deposit(2000)      # Deposited 2000. Balance: 7000
acc1.withdraw(1500)     # Withdrawn 1500. Balance: 5500
acc2.deposit(5000)      # Deposited 5000. Balance: 15000
acc2.withdraw(3000)     # Withdrawn 3000. Balance: 12000

acc1.get_statement()
acc2.get_statement()

# Objects are independent
print(f"Rahul's balance: {acc1.balance}")
print(f"Priya's balance: {acc2.balance}")

Output:

Deposited 2000. Balance: 7000
Withdrawn 1500. Balance: 5500
Deposited 5000. Balance: 15000
Withdrawn 3000. Balance: 12000
--- Statement for Rahul ---
Bank: State Bank of India
Transactions: +2000, -1500
Final Balance: 5500
--- Statement for Priya ---
Bank: State Bank of India
Transactions: +5000, -3000
Final Balance: 12000
Rahul's balance: 5500
Priya's balance: 12000

Common Mistakes to Avoid

  • Forgetting self as the first parameter in methods — causes TypeError.
  • Confusing class variables with instance variables — class variables are shared.
  • Calling a method before defining it in the class body.
  • Using class name to access instance variables — must use object reference.
  • Forgetting that __init__ is called automatically — no need to call it explicitly.

Practice Questions

  1. Create a Circle class with radius attribute and methods to calculate area and circumference.
  2. Write a Car class with class variable tracking total cars manufactured and instance variables for brand, model, and color.
  3. Implement a Counter class with increment, decrement, and reset methods.
  4. Create a Student class that stores marks for 3 subjects and calculates the average.

Key Points

A class is a blueprint; an object is an instance of a class.

__init__ is the constructor method called automatically during object creation.

self refers to the current instance and must be the first parameter of all methods.

Instance variables are unique per object; class variables are shared.

__str__ provides human-readable output; __repr__ provides developer representation.

Each object has its own copy of instance variables but shares class variables.

Python Programming Handwritten Notes

Master Python Programming with Easy Handwritten Notes – Perfect for Interviews, Placements, GATE & Exams.