Sitemap
Anuj Chhetri

Anuj Chhetri

Full Stack Developer specializing in MERN stack and Laravel, currently working Data Science and building real-world projects.

Python — Object-Oriented Programming: Classes, Objects, init, self, Attributes & Validation

4 min readApr 9, 2026

--

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 data

Real-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 definition

What 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 2

How __new__ and __init__ work together:

  1. __new__ — called first, creates the object in memory (allocates space)
  2. __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) # Merry

Are 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 values

Unless 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 attribute

Instance 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 * 40000

Adding 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 Academy

Example 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

--

--

Anuj Chhetri
Anuj Chhetri

Written by Anuj Chhetri

Full Stack Developer specializing in MERN stack and Laravel, currently working Data Science and building real-world projects.