Automatically called when an object is created. init attributes
self
refers to the current object
class Student: # -> Class def __init__(self, name, marks): # -> Constructor self.name = name # -> Attributes self.marks = marks def display(self): # -> Methods always take `self` as the first parameter. print("Name:", self.name) print("Marks:", self.marks)s1 = Student("Om", 85)s1.display()
2️⃣ Encapsulation
Bundling data (variables) and methods (functions) together
Restricting direct access to some data
Type
Syntax
Meaning
Public
name
👉 Accessible everywhere
Protected
_name
👉 Should be used inside class & subclasses. 👉 “it’s accessible but don’t touch unless you know what you’re doing”
Private
__name
👉 Not accessible directly outside class 👉 “it’s accessible but don’t touch unless you know what you’re doing” 👉 possible with __attribute → _ClassName__attribute
Python philosophy that “We are all consenting adults here” which basically means that programmers are given a lot of freedom when it comes to overriding functions and variables, even built-in ones, and that it’s up to the user to use this power responsibly.
class Employee: def __init__(self, name, salary, department): # Public attribute self.name = name # Protected attribute (convention-based) self._department = department # Private attribute self.__salary = salary # Getter (read-only access) def get_salary(self): return self.__salary # Setter (controlled write access) def set_salary(self, amount): if amount < 0: print("❌ Salary cannot be negative") else: self.__salary = amount # Method using private data def show_salary(self): print("Salary:", self.__salary) # Business logic method def increment_salary(self, percent): if percent <= 0: print("❌ Increment must be positive") else: self.__salary += self.__salary * percent / 100 # Method using protected data def show_department(self): print("Department:", self._department)emp = Employee("Om", 50000, "Engineering")# ✅ Public accessprint(emp.name)# ⚠️ Protected access (allowed but discouraged outside class)print(emp._department)# ❌ Private access (should NOT be done)# print(emp.__salary)# print(emp._Employee__salary)# ✅ Controlled accessprint(emp.get_salary())emp.set_salary(55000)emp.show_salary()emp.increment_salary(10)emp.show_salary()emp.show_department()
3️⃣ Inheritance
Reusing existing class functionality
Parent (base) and Child (derived) classes
super() keyword
Types of inheritance:
Single
Multiple
Multilevel
Hierarchical
class Employee(Person): def work(self): print("Working...")
4️⃣ Polymorphism
Same method name, different behavior
Method overriding
Operator overloading
Duck typing
class Dog: def speak(self): print("Bark")class Cat: def speak(self): print("Meow")
5️⃣ Abstraction
Hiding implementation details
Using abc module
Abstract Base Classes (ABC)
@abstractmethod
from abc import ABC, abstractmethodclass Shape(ABC): @abstractmethod def area(self): pass
6️⃣ Special (Magic / Dunder) Methods
__str__(), __repr__()
__len__()
__add__(), __eq__()
__del__()
def __str__(self): return self.name
7️⃣ Class Variables vs Instance Variables
Type
Shared
Example
Instance variable
❌
self.name
Class variable
✅
company_name
8️⃣ Static Methods and Class Methods
@staticmethod
@classmethod
cls keyword
class Math: @staticmethod def add(a, b): return a + b
9️⃣ Composition and Aggregation
Composition: Strong “has-a” relationship
Aggregation: Weak “has-a” relationship
class Engine: passclass Car: def __init__(self): self.engine = Engine()