🐍
Syllabus / Python Programming / Phase 4: OOP Concepts, File Handling, Exception Handling (Days 16–20)
Beginner

Classes, Objects, and the __init__ Constructor

📂 Phase 4: OOP Concepts, File Handling, Exception Handling (Days 16–20) · Python Programming

A class is a blueprint that defines the attributes (data) and methods (behavior) that its objects will have. An object is a concrete instance created from that blueprint, holding its own actual data — everything you have used so far, from strings to lists, is itself an object built from a class.

Defining a Class

class Car:
    pass   # an empty class body — placeholder for now

my_car = Car()   # creating an OBJECT (instance) of the Car class
print(type(my_car))   # 

The __init__ Constructor

__init__ is a special method that runs automatically every time a new object is created — used to set up the object's initial attributes. It is the closest equivalent to a constructor in other languages.

class Car:
    def __init__(self, color, model):
        self.color = color
        self.model = model
        self.speed = 0

my_car = Car("Red", "Civic")
print(my_car.color)   # Red
print(my_car.model)   # Civic

Understanding self

self refers to the specific object the method is being called on — it must be the first parameter of every instance method, though Python passes it automatically; you never type it yourself when calling the method.

class Car:
    def __init__(self, model):
        self.model = model   # self.model belongs to THIS object specifically

    def honk(self):
        print(f"{self.model} says: Beep beep!")

car1 = Car("Civic")
car2 = Car("Mustang")
car1.honk()   # Civic says: Beep beep!
car2.honk()   # Mustang says: Beep beep!
self is not a Python keyword — it is just a strong, near-universal convention. You could technically name it anything, but every Python developer expects to see self, and deviating from it will confuse anyone reading your code.

Instance Methods

class Car:
    def __init__(self, model, speed=0):
        self.model = model
        self.speed = speed

    def accelerate(self, amount):
        self.speed += amount
        print(f"{self.model} is now going {self.speed} km/h")

my_car = Car("Civic")
my_car.accelerate(20)   # Civic is now going 20 km/h
my_car.accelerate(15)   # Civic is now going 35 km/h

Multiple Independent Objects

Every object created from a class has its own separate copy of the instance attributes — modifying one object's data never affects another object of the same class.

car1 = Car("Civic")
car2 = Car("Mustang")

car1.accelerate(30)
print(car1.speed)   # 30
print(car2.speed)   # 0 — completely unaffected by car1

Class Attributes vs Instance Attributes

TypeDefined WhereShared Across Objects?
Class attributeDirectly inside the class, outside any methodYes — same value for every object unless overridden
Instance attributeInside __init__, using self.No — each object has its own independent copy
class Car:
    wheels = 4   # class attribute — same for every Car object

    def __init__(self, model):
        self.model = model   # instance attribute — unique per object

car1 = Car("Civic")
car2 = Car("Mustang")
print(car1.wheels, car2.wheels)   # 4 4 — shared class attribute

The __str__ Method — Controlling How an Object Prints

class Car:
    def __init__(self, model):
        self.model = model

    def __str__(self):
        return f"Car(model={self.model})"

my_car = Car("Civic")
print(my_car)   # Car(model=Civic) — without __str__, this would print something unhelpful
                 # like <__main__.Car object at 0x7f...>
Interview tip: A common question is "What happens if you don't define __init__?" — Python silently uses a default constructor that takes no arguments and does nothing; you would then have to manually assign every attribute on each object after creation, which is rarely practical for anything beyond a trivial class.