What is a Class?
- A class is a blueprint for creating objects.
- It defines attributes (variables) and methods (functions).
class Car:
# class attribute
wheels = 4
# constructor (initializer)
def __init__(self, brand, model):
self.brand = brand # instance attribute
self.model = model
# method (behavior)
def drive(self):
print(f"{self.brand} {self.model} is driving...")
What is an Object?
- An object is an instance of a class.
- You create objects by calling the class like a function.
# Create objects
car1 = Car("Tesla", "Model S")
car2 = Car("BMW", "X5")
# Access attributes
print(car1.brand) # Tesla
print(car2.model) # X5
# Call methods
car1.drive() # Tesla Model S is driving...
The `__init__` Method
- A special method called when a new object is created.
- Used to initialize attributes.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Amr", 30)
print(p.name, p.age) # Amr 30
Instance vs Class Attributes
- Instance attributes → belong to a specific object (
self.attribute). - Class attributes → shared across all instances.
class Dog:
species = "Canine" # class attribute
def __init__(self, name):
self.name = name # instance attribute
dog1 = Dog("Rex")
dog2 = Dog("Buddy")
print(dog1.species, dog1.name) # Canine Rex
print(dog2.species, dog2.name) # Canine Buddy
Methods
- Functions inside classes are called methods.
- They always take
selfas the first parameter (reference to the object).
class Calculator:
def add(self, a, b):
return a + b
calc = Calculator()
print(calc.add(5, 10)) # 15
Special Methods (Magic / Dunder Methods)
Python classes have special methods that begin and end with __.
__init__→ constructor__str__→ string representation__len__→ length of object (if applicable)__add__→ behavior for+operator
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
def __str__(self):
return f"Book: {self.title}"
def __len__(self):
return self.pages
b = Book("Python Basics", 300)
print(b) # Book: Python Basics
print(len(b)) # 300
Multiple Objects
You can create multiple independent objects from the same class.
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
s1 = Student("Ali", "A")
s2 = Student("Sara", "B")
print(s1.name, s1.grade) # Ali A
print(s2.name, s2.grade) # Sara B
Attributes and Methods
In Python’s Object-Oriented Programming (OOP), attributes and methods are the core building blocks of classes and objects. They represent the data (attributes) and the behavior (methods) of an object.
1. Attributes
Attributes are variables inside a class that hold data about an object.
Types of Attributes
- Instance Attributes
- Belong to each object (instance).
- Defined inside
__init__. - Each object can have different values.
class Person:
def __init__(self, name, age):
self.name = name # instance attribute
self.age = age
p1 = Person("Amr", 30)
p2 = Person("Sara", 25)
print(p1.name, p1.age) # Amr 30
print(p2.name, p2.age) # Sara 25
- Class Attributes
- Shared by all objects of the class.
- Defined directly inside the class, not in
__init__.
class Dog:
species = "Canine" # class attribute
def __init__(self, name):
self.name = name # instance attribute
d1 = Dog("Rex")
d2 = Dog("Buddy")
print(d1.name, d1.species) # Rex Canine
print(d2.name, d2.species) # Buddy Canine
Methods
Methods are functions inside classes that define behaviors.
- Always have
selfas the first parameter → refers to the current object.
class Calculator:
def add(self, x, y): # instance method
return x + y
def multiply(self, x, y):
return x * y
calc = Calculator()
print(calc.add(5, 3)) # 8
print(calc.multiply(4, 6)) # 24
Types of Methods
- Instance Methods (most common)
- Use
self. - Can access/modify object attributes.
class Student:
def __init__(self, name):
self.name = name
def introduce(self):
print("Hi, my name is", self.name)
- Class Methods (
@classmethod)
- Take
clsas the first argument. - Work with the class itself, not an instance.
class Employee:
company = "VarApps"
@classmethod
def change_company(cls, new_name):
cls.company = new_name
e1 = Employee()
e2 = Employee()
Employee.change_company("VarThings")
print(e1.company, e2.company) # VarThings VarThings
- Static Methods (
@staticmethod)
- Don’t take
selforcls. - Behave like normal functions but live inside a class.
class MathUtils:
@staticmethod
def square(x):
return x * x
print(MathUtils.square(5)) # 25
Special (Dunder) Methods
Python has special methods that start and end with __.
They customize object behavior.
__init__→ constructor__str__→ string representation__len__→ defines behavior forlen()__add__→ defines behavior for+
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
def __str__(self):
return f"Book: {self.title}"
def __len__(self):
return self.pages
b = Book("Python Basics", 300)
print(b) # Book: Python Basics
print(len(b)) # 300
Accessing and Modifying Attributes
- Access attributes with dot notation.
- Modify them dynamically.
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
car = Car("Tesla", "Model 3")
print(car.brand) # Tesla
car.brand = "BMW" # modify
print(car.brand) # BMW
Special (Magic/Dunder) Methods
In Python, many behaviors of objects are controlled by special methods, also called dunder methods (short for “double underscore”).
These methods let you customize how your objects behave with built-in functions, operators, and syntax.
What are Dunder Methods?
- Dunder = double underscore (e.g.,
__init__,__str__). - They are automatically called by Python in specific situations.
- Allow your classes to behave more like built-in types.
Example:
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
def __str__(self):
return f"Book: {self.title}, {self.pages} pages"
b = Book("Python Basics", 300)
print(b) # Calls __str__ → Book: Python Basics, 300 pages
Commonly Used Special Methods
Object Creation and Initialization
__init__(self, …)→ called when an object is created.__new__(cls, …)→ controls object creation (rarely used directly).
class Person:
def __init__(self, name):
self.name = name
String Representation
__str__→ user-friendly string (used byprint()).__repr__→ official representation (used in debugging, REPL).
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return f"Point({self.x}, {self.y})"
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
p = Point(3, 4)
print(str(p)) # Point(3, 4)
print(repr(p)) # Point(x=3, y=4)
Arithmetic Operators
You can redefine how operators (+, -, *, etc.) work on your objects.
__add__→+__sub__→-__mul__→*__truediv__→/
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
Comparisons
__eq__→==__lt__→<__le__→<=__gt__→>__ge__→>=
class Box:
def __init__(self, volume):
self.volume = volume
def __eq__(self, other):
return self.volume == other.volume
b1 = Box(100)
b2 = Box(100)
print(b1 == b2) # True
Length, Iteration, Indexing
__len__→ forlen(obj).__getitem__→ access withobj[key].__setitem__→ assign withobj[key] = value.__iter__&__next__→ make an object iterable.
class MyList:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
def __getitem__(self, index):
return self.items[index]
lst = MyList([10, 20, 30])
print(len(lst)) # 3
print(lst[1]) # 20
Context Managers
__enter__and__exit__→ allowwith obj:usage.
class FileManager:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, "w")
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
with FileManager("test.txt") as f:
f.write("Hello, dunder methods!")
3. Why Use Dunder Methods?
- Make classes behave like native Python objects.
- Improve readability (
__str__and__repr__). - Enable operator overloading (
+,==, etc.). - Support built-in functions (
len,iter,with, etc.).
Object-Oriented Programming
Object-Oriented Programming (OOP) in Python is built on four pillars:
- Encapsulation
- Inheritance
- Polymorphism
- Abstraction
Let’s go through them one by one with examples.
Encapsulation
Encapsulation means bundling data (attributes) and behavior (methods) into a class, and controlling how they are accessed.
Public, Protected, Private Attributes
- Public: accessible everywhere (default).
- Protected: prefix with
_, used internally (convention). - Private: prefix with
__, not directly accessible (name mangling).
class Account:
def __init__(self, owner, balance):
self.owner = owner # public
self._status = "Active" # protected
self.__balance = balance # private
def deposit(self, amount):
self.__balance += amount
def get_balance(self):
return self.__balance
acc = Account("Amr", 1000)
print(acc.owner) # Amr
print(acc._status) # Should be treated as internal
# print(acc.__balance) # Error
print(acc.get_balance()) # Safe access
Inheritance
Inheritance allows a class (child) to reuse and extend another class’s (parent) attributes and methods.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print("Some sound")
class Dog(Animal):
def speak(self):
print(f"{self.name} says Woof!")
class Cat(Animal):
def speak(self):
print(f"{self.name} says Meow!")
dog = Dog("Rex")
cat = Cat("Mimi")
dog.speak() # Rex says Woof!
cat.speak() # Mimi says Meow!
Benefits: code reuse, hierarchy, easier maintenance.
Polymorphism
Polymorphism means “many forms” — the same method name can have different implementations depending on the object.
Example with Different Classes
class Bird:
def make_sound(self):
print("Chirp")
class Dog:
def make_sound(self):
print("Woof")
animals = [Bird(), Dog()]
for animal in animals:
animal.make_sound()
Example with Inheritance
class Shape:
def area(self):
return 0
class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return 3.14 * self.r * self.r
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side * self.side
shapes = [Circle(5), Square(4)]
for s in shapes:
print(s.area()) # 78.5, 16
Abstraction
Abstraction means hiding unnecessary details and showing only the essential features of an object.
In Python, this is often done with abstract base classes (ABC).
- An abstract class cannot be instantiated.
- It defines abstract methods that must be implemented in child classes.
from abc import ABC, abstractmethod
class Vehicle(ABC): # abstract base class
@abstractmethod
def start(self):
pass
class Car(Vehicle):
def start(self):
print("Car engine started")
class Bike(Vehicle):
def start(self):
print("Bike started with a kick")
# vehicle = Vehicle() # Error: can't instantiate abstract class
car = Car()
bike = Bike()
car.start() # Car engine started
bike.start() # Bike started with a kick
Benefits: ensures **consistent interfaces** for child classes.
Summary
- Encapsulation → bundles data & behavior, controls access (public/protected/private).
- Inheritance → reuse and extend code across classes.
- Polymorphism → same method name, different behaviors.
- Abstraction → define abstract interfaces, hide unnecessary details.
Together, these four pillars make Python’s OOP powerful, modular, and maintainable.