Python — Object-Oriented Programming: Classes, Objects, init, self, Attributes & Validation
Python evolves from simple instructions into a structured system of objects — the foundation of real-world software development.
What Is Object-Oriented Programming?
Programming has two major paradigms:
Imperative — you tell the computer how to do something, step by step
Declarative — you tell the computer what you want, not how to do it
OOP — a programming paradigm built on the concept of objects
OOP definition:
OOP is a programming paradigm that provides a way of structuring programs so that properties and behaviors are bundled together into individual objects.
Properties -> variables (what the object HAS)
Behaviors -> methods (what the object DOES)
OOP -> based on the concepts of objects
Objects -> contain data AND code that manipulates that dataReal-world example: A BankAccount object has a name, ID, balance (properties) and does deposit, withdraw, calculate interest (behaviors). Everything about a bank account is bundled together in one place.
What Is a Class?
A class is a blueprint or template for creating objects. It defines what attributes and methods every object of that type will have — but it doesn’t create any data itself.
# Syntax
class ClassName:
# class definitionWhat Is an Object (Instance)?
An object is a specific instance created from a class blueprint. When you call the class like a function — Python creates an object.
The init Method
__init__ is a special method (also called initializer). It runs automatically when a new object is created and sets the initial state of the object by assigning values to its attributes.
class Cat:
def __init__(self, name, age): # Special method / initializer
self.name = name # instance attribute
self.age = age # instance attribute
print(self.name, self.age)
cat1 = Cat("Furry", 2) # __init__ runs automatically
# Output: Furry 2How __new__ and __init__ work together:
__new__— called first, creates the object in memory (allocates space)__init__— called second, fills the object with its starting values
You almost never write __new__ yourself. Python handles it. You write __init__ to set up your object.
The self Parameter
self represents the current object instance. When you write self.name = name, you're saying "attach this name value to this specific object."
Class Attributes vs Instance Attributes
This is one of the most important distinctions in OOP.
class Cat:
species = "Felis catus" # Class attribute — SHARED by all objects
def __init__(self, name, age):
self.name = name # Instance attribute — UNIQUE per object
self.age = age
cat1 = Cat("Furry", 2)
cat2 = Cat("Merry", 1)
# Class attribute — same for all
print(Cat.species) # Felis catus
print(cat1.species) # Felis catus - inherits from class
# Instance attributes — different per object
print(cat1.name) # Furry
print(cat2.name) # MerryAre two objects with the same values equal?
x = Cat("Fury", 2)
y = Cat("Fury", 2)
x == y # False - different objects in memory, even with same valuesUnless you define __eq__, Python compares by memory location, not by value.
Get Anuj Chhetri’s stories in your inbox
Join Medium for free to get updates from this writer.
Class Attribute vs Instance Attribute — The Difference
class New:
x = 123 # class attribute
def __init__(self):
self.x = 1234 # instance attribute (different!)
print(New.x) # 123 - accessing class attribute
print(New().x) # 1234 - creating an instance, accessing instance attributeInstance Methods
Instance methods are functions defined inside a class that operate on the object’s data. They always take self as the first parameter.
class Cat:
species = "Felis catus"
def __init__(self, name, age):
self.name = name
self.age = age
# Instance method 1
def describe(self):
return f"{self.name} is {self.age} year old."
# Instance method 2 — accepts extra argument
def voice(self, sound):
return f"{self.name} says {sound}"
cat3 = Cat("Merry", 1)
cat3.describe() # 'Merry is 1 year old.'
cat3.voice("Meow") # 'Merry says Meow'Example 1 — BankAccount Class
class BankAccount:
def __init__(self, name, ids, index=0.0, balance=0.0):
self.name = name
self.ids = ids
self.index = index
self.balance = balance
def totalUserBalance(self):
return self.index * self.balance
account1 = BankAccount("Ram", 1001)
tot = account1.totalUserBalance()
print(tot) # 0.0 - index=0, balance=0 by default
account2 = BankAccount("Gita", 1002, 2, 40000)
account2.totalUserBalance() # 80000 <- 2 * 40000Adding Validation with assert
assert checks a condition inside __init__. If the condition fails — it raises an AssertionError before the object is created. This protects your class from invalid data.
class BankAccount:
def __init__(self, name: str, ids: int, index=0.0, balance=0.0):
# Validation
assert ids >= 1000, f"Id number {ids} is not greater than or equal to 1000"
assert index >= 0, f"Index should be greater or equal to 0, your pass {index}"
assert balance >= 0, f"Balance is less than 0"
self.name = name
self.ids = ids
self.index = index
self.balance = balance
def totalUserBalance(self):
return self.index * self.balance
# Valid object
account2 = BankAccount("Gita", 1002, 2, 40000)
account2.totalUserBalance() # 80000
# Invalid — id too small
account5 = BankAccount("Ram", 1005, -1)
# AssertionError: ← index is -1, fails assert index >= 0
# Invalid — id too small
account5 = BankAccount("Ram", 500)
# AssertionError: Id number 500 is not greater than or equal to 1000 Example 2— Student Class
class Student:
school = "MY Academy" # class attribute
def __init__(self, name: str, marks: int):
assert 0 <= marks <= 100, "Marks must be between 0 and 100"
self.name = name
self.marks = marks
def grade(self):
if self.marks >= 90: return "A"
elif self.marks >= 75: return "B"
elif self.marks >= 60: return "C"
else: return "F"
def report(self):
return f"{self.name} | Marks: {self.marks} | Grade: {self.grade()}"
s1 = Student("Ram", 85)
s2 = Student("Gita", 92)
print(s1.report()) # Ram | Marks: 85 | Grade: B
print(s2.report()) # Gita | Marks: 92 | Grade: A
print(Student.school) # MY AcademyExample 3 — Product in a Shop
class Product:
def __init__(self, name: str, price: float, quantity: int):
assert price > 0, "Price must be positive"
assert quantity >= 0, "Quantity cannot be negative"
self.name = name
self.price = price
self.quantity = quantity
def total_value(self):
return self.price * self.quantity
def apply_discount(self, percent: float):
self.price = self.price * (1 - percent / 100)
return self.price
def info(self):
return f"{self.name} | Price: {self.price} | Stock: {self.quantity}"
p1 = Product("Laptop", 50000, 10)
print(p1.info()) # Laptop | Price: 50000 | Stock: 10
print(p1.total_value()) # 500000
print(p1.apply_discount(10)) # 45000.0 





