📓 Python Programming - Object-Oriented Programming (OOP)
1. Classes & Objects
What is OOP?
-
Organizes code around real-world entities (objects).
-
Class: Blueprint for creating objects.
-
Object: An instance of a class.
# Defining a class
class Student:
pass
# Creating an object
s1 = Student()
print(type(s1))
2. Class vs Object Attributes
-
Class attributes: Shared by all objects.
-
Object attributes: Unique to each object.
➤ Example:
class Student:
college = "Westcliff University" # Class attribute
def __init__(self, name, age):
self.name = name # Object attribute
self.age = age
s1 = Student("Alice", 20)
s2 = Student("Bob", 22)
print(s1.name, s1.college)
print(s2.name, s2.college)
3. Initializing Object Attributes with __init__()
__init__() is a special method that runs when an object is created. (Like Constructor)
➤ Example:
class Car:
def __init__(self, brand, year):
self.brand = brand
self.year = year
c1 = Car("Toyota", 2020)
print(c1.brand, c1.year)
4. The self Keyword
-
Refers to the current object.
-
Used to access object attributes and methods.
➤ Example:
class Dog:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} says Woof!")
d = Dog("Buddy")
d.speak()
5. Inheritance
Inheritance allows a class to use properties and methods of another class.
Types: - Single Inheritance
-
Multiple Inheritance
-
Multi-Level Inheritance
➤ Single Inheritance:
class Animal:
def speak(self):
print("Animal sound")
class Dog(Animal):
def bark(self):
print("Woof!")
d = Dog()
d.speak()
d.bark()
➤ Multiple Inheritance:
class A:
def show_a(self):
print("A")
class B:
def show_b(self):
print("B")
class C(A, B):
pass
c = C()
c.show_a()
c.show_b()
➤ Multi-Level Inheritance:
class Grandparent:
def grandparent_func(self):
print("Grandparent")
class Parent(Grandparent):
def parent_func(self):
print("Parent")
class Child(Parent):
def child_func(self):
print("Child")
c = Child()
c.grandparent_func()
c.parent_func()
c.child_func()
6. Polymorphism & Operator Overloading
Polymorphism:
- Same function name but different behaviors.
Operator Overloading:
- Using special methods to define how operators behave for custom objects.
➤ Polymorphism Example:
class Cat:
def sound(self):
print("Meow")
class Dog:
def sound(self):
print("Woof")
animals = [Cat(), Dog()]
for animal in animals:
animal.sound()
Traditional Overloading Does Not Work in Python
def greet(name):
print(f"Hello, {name}!")
def greet(name, age):
print(f"Hello, {name}! You are {age} years old.")
greet("Alice") # ❌ TypeError: greet() missing 1 required positional argument: 'age'
➤ Python's Way of Function Overloading:
Python does not support function overloading in the traditional sense, as seen in languages like C++ or Java. In those languages, you can have multiple functions with the same name but different parameters (type or number). Python, however, allows you to define a function only once. If you define it again, it simply overwrites the previous definition.
But there is some work around this. Discuss how to implement function overloading in Python using default arguments or variable-length arguments.
# Using default arguments for overloading-like behavior
def greet(name, age=None):
if age is None:
print(f"Hello, {name}!")
else:
print(f"Hello, {name}! You are {age} years old.")
greet("Alice") # Works!
greet("Bob", 25) # Also works!
# Using *args for variable arguments
def add(*numbers):
return sum(numbers)
print(add(1, 2)) # 3
print(add(1, 2, 3, 4)) # 10
➤ Operator Overloading Example:
class Point:
def __init__(self, x):
self.x = x
def __add__(self, other):
return Point(self.x + other.x)
p1 = Point(5)
p2 = Point(10)
result = p1 + p2
print(result.x)
7. Function Overriding & Encapsulation
Function Overriding:
- Child class redefines a method from the parent class.
Encapsulation:
- Restricts direct access to variables using private/protected attributes.
➤ Function Overriding Example:
class Animal:
def speak(self):
print("Animal sound")
class Dog(Animal):
def speak(self):
print("Woof!")
d = Dog()
d.speak()
➤ Encapsulation Example:
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private attribute
def get_balance(self):
return self.__balance
def deposit(self, amount):
self.__balance += amount
acc = BankAccount(1000)
acc.deposit(500)
print(acc.get_balance())
➤ Using Properties (Getters & Setters):
class Temperature:
def __init__(self):
self._celsius = 0
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Temperature below absolute zero is not possible")
self._celsius = value
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
# Usage
temp = Temperature()
temp.celsius = 25
print(f"Celsius: {temp.celsius}")
print(f"Fahrenheit: {temp.fahrenheit}")
8. Class Methods & Static Methods
Class Methods & Static Methods:
- Instance Method: Needs
self, works with object data - Class Method: Uses
@classmethod, getsclsparameter - Static Method: Uses
@staticmethod, independent function inside class
➤ Example:
class Person:
species = "Homo sapiens" # Class attribute
def __init__(self, name, age):
self.name = name # Instance attribute
self.age = age
# Instance method
def introduce(self):
return f"Hi, I'm {self.name}, {self.age} years old"
# Class method
@classmethod
def get_species(cls):
return cls.species
@classmethod
def create_baby(cls, name):
return cls(name, 0) # Alternative constructor
# Static method
@staticmethod
def is_adult(age):
return age >= 18
# Usage examples
p1 = Person("Alice", 25)
print(p1.introduce()) # Instance method
print(Person.get_species()) # Class method
baby = Person.create_baby("Bob") # Class method as constructor
print(baby.introduce())
print(Person.is_adult(17)) # Static method
print(Person.is_adult(20))
9. Special Methods (Magic Methods)
Special methods start and end with double underscores __
Common ones:
- __str__() → What users see when printing
- __repr__() → Developer representation
- __len__() → Makes objects work with len()
- __eq__() → Custom equality comparison
➤ Example:
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
def __str__(self):
return f"Book: {self.title}"
def __repr__(self):
return f"Book('{self.title}', {self.pages})"
def __len__(self):
return self.pages
def __eq__(self, other):
return self.title == other.title
# Testing special methods
book1 = Book("Python Guide", 300)
book2 = Book("Python Guide", 350)
print(book1) # Uses __str__
print(repr(book1)) # Uses __repr__
print(len(book1)) # Uses __len__
print(book1 == book2) # Uses __eq__
# Create a list of books
books = [book1, Book("Java Guide", 400)]
print(f"Total pages: {sum(len(book) for book in books)}")
10. Composition vs Inheritance
Two ways to reuse code:
- Inheritance: "IS-A" relationship (Dog IS-A Animal)
- Composition: "HAS-A" relationship (Car HAS-A Engine)
➤ Composition Example:
# Composition example
class Engine:
def __init__(self, horsepower):
self.horsepower = horsepower
def start(self):
return f"Engine with {self.horsepower}hp started!"
class Car:
def __init__(self, brand, engine):
self.brand = brand
self.engine = engine # Car HAS-A Engine
def start_car(self):
return f"{self.brand}: {self.engine.start()}"
# Usage
engine = Engine(200)
car = Car("Toyota", engine)
print(car.start_car())
# Compare with inheritance approach
class Animal:
def breathe(self):
return "Breathing..."
class Dog(Animal): # Dog IS-A Animal
def bark(self):
return "Woof!"
dog = Dog()
print(dog.breathe()) # Inherited method
print(dog.bark()) # Own method
🎯 Quick Recap
✅ OOP models real-world entities with classes and objects
✅ Class attributes shared, object attributes unique
✅ Inheritance promotes code reuse (IS-A relationship)
✅ Composition provides flexibility (HAS-A relationship)
✅ Polymorphism enables flexibility through method overriding
✅ Properties provide clean encapsulation with getters/setters
✅ Class methods and static methods offer different functionality levels
✅ Special methods make objects work with built-in functions
✅ Function overriding & encapsulation provide safety and control
📝 Practice Exercises
Basic Level:
-
Create a
Carclass with brand and year, and display details using a method. -
Demonstrate single inheritance with
Animal→Bird. -
Create a class
Rectanglethat overloads+to add areas of two rectangles. -
Use encapsulation to hide a bank account's balance and provide methods to deposit/withdraw.
-
Demonstrate function overriding using a parent-child class structure.
Intermediate Level:
-
Create a
Personclass with properties for name and age validation. -
Build a
Calculatorclass with static methods for basic operations. -
Create a
Bookclass with special methods (__str__,__len__,__eq__). -
Use composition to create a
Computerclass that HAS-AProcessor. -
Create a class method that works as an alternative constructor.
Advanced Level:
-
Build a complete
BankAccountsystem with properties and validation. -
Create a polymorphic system with different
Shapeclasses. -
Implement a
Libraryclass that managesBookobjects using composition. -
Design a class hierarchy for different types of vehicles with proper inheritance.
-
Create a
Temperatureclass with property decorators for Celsius/Fahrenheit conversion.