Object-Oriented Programming
Classes and Objects
Class Definition and Instantiation
# Basic class definition
class Person:
def __init__(self, name, age):
self.name = name # Instance variable
self.age = age
def greet(self):
return f"Hello, I'm {self.name}"
# Creating objects (instantiation)
person1 = Person("Alice", 30)
person2 = Person("Bob", 25)
# Accessing attributes and methods
print(person1.name) # "Alice"
print(person1.greet()) # "Hello, I'm Alice"
Class vs Instance Attributes
class Dog:
species = "Canis lupus" # Class variable (shared by all instances)
def __init__(self, name, breed):
self.name = name # Instance variable (unique to each instance)
self.breed = breed
def bark(self):
return f"{self.name} says woof!"
# Usage
dog1 = Dog("Buddy", "Golden Retriever")
dog2 = Dog("Max", "German Shepherd")
print(dog1.species) # "Canis lupus" (same for all dogs)
print(dog1.name) # "Buddy" (unique to dog1)
print(Dog.species) # Access class variable via class name
Constructor and Self
class BankAccount:
def __init__(self, account_number, initial_balance=0):
self.account_number = account_number
self.balance = initial_balance
self.transactions = []
def deposit(self, amount):
self.balance += amount
self.transactions.append(f"Deposited ${amount}")
def withdraw(self, amount):
if amount <= self.balance:
self.balance -= amount
self.transactions.append(f"Withdrew ${amount}")
else:
raise ValueError("Insufficient funds")
def get_balance(self):
return self.balance
# Usage
account = BankAccount("123456789", 1000)
account.deposit(500)
account.withdraw(200)
print(account.get_balance()) # 1300
Instance Variables and Methods
Instance Variables
class Student:
def __init__(self, name, student_id):
self.name = name
self.student_id = student_id
self.grades = []
self.enrolled_courses = []
def add_grade(self, subject, grade):
self.grades.append((subject, grade))
def enroll_course(self, course):
self.enrolled_courses.append(course)
def get_average_grade(self):
if not self.grades:
return 0
return sum(grade for _, grade in self.grades) / len(self.grades)
# Each instance has its own variables
student1 = Student("Alice", "S001")
student2 = Student("Bob", "S002")
student1.add_grade("Math", 85)
student1.add_grade("Physics", 92)
student2.add_grade("Math", 78)
print(student1.get_average_grade()) # 88.5
print(student2.get_average_grade()) # 78.0
Instance Methods
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
"""Calculate area - instance method"""
return self.width * self.height
def perimeter(self):
"""Calculate perimeter - instance method"""
return 2 * (self.width + self.height)
def scale(self, factor):
"""Scale the rectangle - modifies instance"""
self.width *= factor
self.height *= factor
def is_square(self):
"""Check if rectangle is a square"""
return self.width == self.height
# Usage
rect = Rectangle(4, 5)
print(rect.area()) # 20
print(rect.perimeter()) # 18
print(rect.is_square()) # False
rect.scale(2)
print(rect.area()) # 80
Class Variables and Methods
Class Variables
class Employee:
company_name = "Tech Corp" # Class variable
employee_count = 0 # Class variable
salary_multiplier = 1.0 # Class variable
def __init__(self, name, salary):
self.name = name # Instance variable
self.salary = salary # Instance variable
Employee.employee_count += 1 # Modify class variable
def get_annual_salary(self):
return self.salary * Employee.salary_multiplier * 12
@classmethod
def get_employee_count(cls):
return cls.employee_count
@classmethod
def set_salary_multiplier(cls, multiplier):
cls.salary_multiplier = multiplier
# Usage
emp1 = Employee("Alice", 5000)
emp2 = Employee("Bob", 6000)
print(Employee.get_employee_count()) # 2
print(emp1.company_name) # "Tech Corp"
Employee.set_salary_multiplier(1.1)
print(emp1.get_annual_salary()) # 66000.0
Class Methods
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_string(cls, person_str):
"""Alternative constructor - class method"""
name, age = person_str.split('-')
return cls(name, int(age))
@classmethod
def from_dict(cls, data):
"""Another alternative constructor"""
return cls(data['name'], data['age'])
@classmethod
def get_species(cls):
"""Class method returning class information"""
return "Homo sapiens"
# Usage - different ways to create objects
person1 = Person("Alice", 30) # Regular constructor
person2 = Person.from_string("Bob-25") # Class method
person3 = Person.from_dict({"name": "Charlie", "age": 35}) # Class method
print(Person.get_species()) # "Homo sapiens"
Static Methods
Static Method Definition
class MathUtils:
@staticmethod
def add(a, b):
"""Static method - doesn't need class or instance"""
return a + b
@staticmethod
def is_prime(n):
"""Check if number is prime"""
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
@staticmethod
def factorial(n):
"""Calculate factorial"""
if n <= 1:
return 1
return n * MathUtils.factorial(n - 1)
# Usage - can call without creating instance
print(MathUtils.add(5, 3)) # 8
print(MathUtils.is_prime(17)) # True
print(MathUtils.factorial(5)) # 120
# Or with instance (but not recommended)
utils = MathUtils()
print(utils.add(2, 3)) # 5
Static vs Class vs Instance Methods
class Example:
class_var = "class variable"
def __init__(self, value):
self.instance_var = value
def instance_method(self):
"""Has access to self and class"""
return f"Instance: {self.instance_var}, Class: {self.class_var}"
@classmethod
def class_method(cls):
"""Has access to class but not instance"""
return f"Class: {cls.class_var}"
@staticmethod
def static_method():
"""No access to class or instance"""
return "Static method - no access to class or instance"
# Usage
obj = Example("instance value")
print(obj.instance_method()) # Instance: instance value, Class: class variable
print(Example.class_method()) # Class: class variable
print(Example.static_method()) # Static method - no access to class or instance
Inheritance
Single Inheritance
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def speak(self):
return f"{self.name} makes a sound"
def move(self):
return f"{self.name} moves"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name, "Canis lupus") # Call parent constructor
self.breed = breed
def speak(self):
return f"{self.name} barks"
def fetch(self):
return f"{self.name} fetches the ball"
class Cat(Animal):
def __init__(self, name, color):
super().__init__(name, "Felis catus")
self.color = color
def speak(self):
return f"{self.name} meows"
def climb(self):
return f"{self.name} climbs the tree"
# Usage
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers", "Gray")
print(dog.speak()) # "Buddy barks"
print(cat.speak()) # "Whiskers meows"
print(dog.move()) # "Buddy moves" (inherited)
print(dog.fetch()) # "Buddy fetches the ball"
Multiple Inheritance
class Flyable:
def fly(self):
return "Flying through the air"
def land(self):
return "Landing safely"
class Swimmable:
def swim(self):
return "Swimming in water"
def dive(self):
return "Diving underwater"
class Bird(Animal, Flyable):
def __init__(self, name, wingspan):
super().__init__(name, "Aves")
self.wingspan = wingspan
class Duck(Bird, Swimmable):
def __init__(self, name, wingspan):
super().__init__(name, wingspan)
def speak(self):
return f"{self.name} quacks"
# Usage
duck = Duck("Donald", 24)
print(duck.speak()) # "Donald quacks"
print(duck.fly()) # "Flying through the air"
print(duck.swim()) # "Swimming in water"
print(duck.move()) # "Donald moves" (from Animal)
# Method Resolution Order (MRO)
print(Duck.__mro__) # Shows the order of method resolution
Inheritance with Complex Hierarchies
class Vehicle:
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
def start(self):
return f"{self.brand} {self.model} is starting"
def stop(self):
return f"{self.brand} {self.model} is stopping"
class Car(Vehicle):
def __init__(self, brand, model, year, doors):
super().__init__(brand, model, year)
self.doors = doors
def honk(self):
return "Beep beep!"
class ElectricCar(Car):
def __init__(self, brand, model, year, doors, battery_capacity):
super().__init__(brand, model, year, doors)
self.battery_capacity = battery_capacity
def charge(self):
return f"Charging {self.brand} {self.model}"
def start(self):
return f"{self.brand} {self.model} is starting silently"
# Usage
tesla = ElectricCar("Tesla", "Model 3", 2023, 4, 75)
print(tesla.start()) # "Tesla Model 3 is starting silently"
print(tesla.charge()) # "Charging Tesla Model 3"
print(tesla.honk()) # "Beep beep!" (inherited from Car)
Method Overriding and super()
Method Overriding
class Shape:
def __init__(self, color):
self.color = color
def area(self):
raise NotImplementedError("Subclass must implement area method")
def describe(self):
return f"A {self.color} shape with area {self.area()}"
class Circle(Shape):
def __init__(self, color, radius):
super().__init__(color)
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
class Rectangle(Shape):
def __init__(self, color, width, height):
super().__init__(color)
self.width = width
self.height = height
def area(self):
return self.width * self.height
def describe(self):
# Override parent method with additional info
base_description = super().describe()
return f"{base_description} (Rectangle: {self.width}x{self.height})"
# Usage
circle = Circle("red", 5)
rectangle = Rectangle("blue", 4, 6)
print(circle.describe()) # "A red shape with area 78.53975"
print(rectangle.describe()) # "A blue shape with area 24 (Rectangle: 4x6)"
Advanced super() Usage
class A:
def __init__(self):
print("A.__init__")
def method(self):
print("A.method")
class B(A):
def __init__(self):
print("B.__init__")
super().__init__()
def method(self):
print("B.method")
super().method()
class C(A):
def __init__(self):
print("C.__init__")
super().__init__()
def method(self):
print("C.method")
super().method()
class D(B, C):
def __init__(self):
print("D.__init__")
super().__init__()
def method(self):
print("D.method")
super().method()
# Usage
d = D()
# Output:
# D.__init__
# B.__init__
# C.__init__
# A.__init__
d.method()
# Output:
# D.method
# B.method
# C.method
# A.method
Magic Methods (Dunder Methods)
Common Magic Methods
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __str__(self):
"""String representation for humans"""
return f"'{self.title}' by {self.author}"
def __repr__(self):
"""String representation for developers"""
return f"Book('{self.title}', '{self.author}', {self.pages})"
def __len__(self):
"""Length of the book"""
return self.pages
def __eq__(self, other):
"""Equality comparison"""
if not isinstance(other, Book):
return False
return (self.title == other.title and
self.author == other.author)
def __lt__(self, other):
"""Less than comparison (for sorting)"""
return self.pages < other.pages
def __add__(self, other):
"""Add two books (combine pages)"""
if isinstance(other, Book):
return self.pages + other.pages
return NotImplemented
# Usage
book1 = Book("1984", "George Orwell", 328)
book2 = Book("Animal Farm", "George Orwell", 112)
print(str(book1)) # "'1984' by George Orwell"
print(repr(book1)) # "Book('1984', 'George Orwell', 328)"
print(len(book1)) # 328
print(book1 == book2) # False
print(book1 < book2) # False
print(book1 + book2) # 440
Arithmetic Magic Methods
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other):
"""Vector addition"""
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
"""Vector subtraction"""
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
"""Scalar multiplication"""
return Vector(self.x * scalar, self.y * scalar)
def __truediv__(self, scalar):
"""Scalar division"""
return Vector(self.x / scalar, self.y / scalar)
def __abs__(self):
"""Vector magnitude"""
return (self.x ** 2 + self.y ** 2) ** 0.5
def __neg__(self):
"""Unary negation"""
return Vector(-self.x, -self.y)
# Usage
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2) # Vector(4, 6)
print(v1 - v2) # Vector(2, 2)
print(v1 * 2) # Vector(6, 8)
print(v1 / 2) # Vector(1.5, 2.0)
print(abs(v1)) # 5.0
print(-v1) # Vector(-3, -4)
Container Magic Methods
class CustomList:
def __init__(self):
self._items = []
def __len__(self):
return len(self._items)
def __getitem__(self, index):
return self._items[index]
def __setitem__(self, index, value):
self._items[index] = value
def __delitem__(self, index):
del self._items[index]
def __contains__(self, item):
return item in self._items
def __iter__(self):
return iter(self._items)
def append(self, item):
self._items.append(item)
# Usage
custom_list = CustomList()
custom_list.append(1)
custom_list.append(2)
custom_list.append(3)
print(len(custom_list)) # 3
print(custom_list[0]) # 1
print(2 in custom_list) # True
for item in custom_list:
print(item) # 1, 2, 3
Properties and Descriptors
Properties with @property
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
"""Get temperature in Celsius"""
return self._celsius
@celsius.setter
def celsius(self, value):
"""Set temperature in Celsius"""
if value < -273.15:
raise ValueError("Temperature below absolute zero is not possible")
self._celsius = value
@property
def fahrenheit(self):
"""Get temperature in Fahrenheit"""
return (self._celsius * 9/5) + 32
@fahrenheit.setter
def fahrenheit(self, value):
"""Set temperature in Fahrenheit"""
self.celsius = (value - 32) * 5/9
@property
def kelvin(self):
"""Get temperature in Kelvin"""
return self._celsius + 273.15
# Usage
temp = Temperature(25)
print(temp.celsius) # 25
print(temp.fahrenheit) # 77.0
print(temp.kelvin) # 298.15
temp.fahrenheit = 100
print(temp.celsius) # 37.77777777777778
Computed Properties
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def diameter(self):
return self._radius * 2
@property
def area(self):
return 3.14159 * self._radius ** 2
@property
def circumference(self):
return 2 * 3.14159 * self._radius
# Usage
circle = Circle(5)
print(circle.radius) # 5
print(circle.diameter) # 10
print(circle.area) # 78.53975
print(circle.circumference) # 31.4159
circle.radius = 3
print(circle.area) # 28.274309999999996
Descriptors
class Validator:
def __init__(self, min_value=None, max_value=None):
self.min_value = min_value
self.max_value = max_value
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
if self.min_value is not None and value < self.min_value:
raise ValueError(f"{self.name} must be >= {self.min_value}")
if self.max_value is not None and value > self.max_value:
raise ValueError(f"{self.name} must be <= {self.max_value}")
instance.__dict__[self.name] = value
class Person:
age = Validator(min_value=0, max_value=150)
height = Validator(min_value=0)
def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height
# Usage
person = Person("Alice", 30, 170)
print(person.age) # 30
try:
person.age = -5 # Raises ValueError
except ValueError as e:
print(e) # "age must be >= 0"
Abstract Base Classes
Using abc Module
from abc import ABC, abstractmethod
class Animal(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def speak(self):
"""Abstract method - must be implemented by subclasses"""
pass
@abstractmethod
def move(self):
"""Abstract method - must be implemented by subclasses"""
pass
def introduce(self):
"""Concrete method - can be inherited"""
return f"I am {self.name}"
class Dog(Animal):
def speak(self):
return f"{self.name} barks"
def move(self):
return f"{self.name} runs"
class Fish(Animal):
def speak(self):
return f"{self.name} cannot speak"
def move(self):
return f"{self.name} swims"
# Usage
# animal = Animal("Generic") # TypeError: Can't instantiate abstract class
dog = Dog("Buddy")
fish = Fish("Nemo")
print(dog.speak()) # "Buddy barks"
print(fish.move()) # "Nemo swims"
print(dog.introduce()) # "I am Buddy"
Abstract Properties
from abc import ABC, abstractmethod
class Shape(ABC):
@property
@abstractmethod
def area(self):
"""Abstract property - must be implemented"""
pass
@property
@abstractmethod
def perimeter(self):
"""Abstract property - must be implemented"""
pass
def describe(self):
return f"Area: {self.area}, Perimeter: {self.perimeter}"
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
@property
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14159 * self.radius ** 2
@property
def perimeter(self):
return 2 * 3.14159 * self.radius
# Usage
rectangle = Rectangle(4, 5)
circle = Circle(3)
print(rectangle.describe()) # "Area: 20, Perimeter: 18"
print(circle.describe()) # "Area: 28.274309999999996, Perimeter: 18.84954"
Polymorphism and Encapsulation
Polymorphism
class Animal:
def speak(self):
raise NotImplementedError("Subclass must implement speak method")
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
class Cow(Animal):
def speak(self):
return "Moo!"
def animal_sounds(animals):
"""Polymorphic function - works with any Animal subclass"""
for animal in animals:
print(f"{animal.__class__.__name__} says: {animal.speak()}")
# Usage
animals = [Dog(), Cat(), Cow()]
animal_sounds(animals)
# Output:
# Dog says: Woof!
# Cat says: Meow!
# Cow says: Moo!
Encapsulation (Private Members)
class BankAccount:
def __init__(self, account_number, initial_balance=0):
self._account_number = account_number # Protected (convention)
self.__balance = initial_balance # Private (name mangling)
self.__transactions = [] # Private
def deposit(self, amount):
if amount > 0:
self.__balance += amount
self.__transactions.append(f"Deposited: ${amount}")
else:
raise ValueError("Deposit amount must be positive")
def withdraw(self, amount):
if amount > 0 and amount <= self.__balance:
self.__balance -= amount
self.__transactions.append(f"Withdrew: ${amount}")
else:
raise ValueError("Invalid withdrawal amount")
def get_balance(self):
"""Public method to access private balance"""
return self.__balance
def get_statement(self):
"""Public method to access private transactions"""
return self.__transactions.copy()
def _internal_method(self):
"""Protected method (convention)"""
return "Internal processing"
# Usage
account = BankAccount("123456789", 1000)
account.deposit(500)
account.withdraw(200)
print(account.get_balance()) # 1300
print(account.get_statement()) # ['Deposited: $500', 'Withdrew: $200']
# These will raise AttributeError:
# print(account.__balance) # AttributeError
# print(account.__transactions) # AttributeError
# But name mangling allows access (not recommended):
print(account._BankAccount__balance) # 1300
Data Hiding with Properties
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str) or len(value) < 1:
raise ValueError("Name must be a non-empty string")
self._name = value
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int) or value < 0:
raise ValueError("Age must be a non-negative integer")
self._age = value
@property
def is_adult(self):
"""Read-only computed property"""
return self._age >= 18
# Usage
person = Person("Alice", 25)
print(person.name) # "Alice"
print(person.age) # 25
print(person.is_adult) # True
person.name = "Bob" # OK
person.age = 30 # OK
try:
person.age = -5 # Raises ValueError
except ValueError as e:
print(e) # "Age must be a non-negative integer"
Design Patterns
Factory Pattern
from abc import ABC, abstractmethod
class Vehicle(ABC):
@abstractmethod
def start(self):
pass
@abstractmethod
def stop(self):
pass
class Car(Vehicle):
def start(self):
return "Car engine started"
def stop(self):
return "Car engine stopped"
class Motorcycle(Vehicle):
def start(self):
return "Motorcycle engine started"
def stop(self):
return "Motorcycle engine stopped"
class Bicycle(Vehicle):
def start(self):
return "Ready to pedal"
def stop(self):
return "Stopped pedaling"
class VehicleFactory:
@staticmethod
def create_vehicle(vehicle_type):
if vehicle_type.lower() == "car":
return Car()
elif vehicle_type.lower() == "motorcycle":
return Motorcycle()
elif vehicle_type.lower() == "bicycle":
return Bicycle()
else:
raise ValueError(f"Unknown vehicle type: {vehicle_type}")
# Usage
factory = VehicleFactory()
car = factory.create_vehicle("car")
bike = factory.create_vehicle("bicycle")
print(car.start()) # "Car engine started"
print(bike.start()) # "Ready to pedal"
Singleton Pattern
class Singleton:
_instance = None
_initialized = False
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not self._initialized:
self.value = 0
self._initialized = True
def increment(self):
self.value += 1
def get_value(self):
return self.value
# Usage
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True (same instance)
s1.increment()
print(s2.get_value()) # 1 (shared state)
Observer Pattern
class Subject:
def __init__(self):
self._observers = []
self._state = None
def attach(self, observer):
self._observers.append(observer)
def detach(self, observer):
self._observers.remove(observer)
def notify(self):
for observer in self._observers:
observer.update(self._state)
def set_state(self, state):
self._state = state
self.notify()
class Observer:
def __init__(self, name):
self.name = name
def update(self, state):
print(f"{self.name} received update: {state}")
class NewsAgency(Subject):
def __init__(self):
super().__init__()
self._news = ""
def set_news(self, news):
self._news = news
self.set_state(news)
def get_news(self):
return self._news
# Usage
news_agency = NewsAgency()
observer1 = Observer("CNN")
observer2 = Observer("BBC")
observer3 = Observer("Reuters")
news_agency.attach(observer1)
news_agency.attach(observer2)
news_agency.attach(observer3)
news_agency.set_news("Breaking: Python 4.0 Released!")
# Output:
# CNN received update: Breaking: Python 4.0 Released!
# BBC received update: Breaking: Python 4.0 Released!
# Reuters received update: Breaking: Python 4.0 Released!
Strategy Pattern
from abc import ABC, abstractmethod
class SortingStrategy(ABC):
@abstractmethod
def sort(self, data):
pass
class BubbleSort(SortingStrategy):
def sort(self, data):
data = data.copy()
n = len(data)
for i in range(n):
for j in range(0, n - i - 1):
if data[j] > data[j + 1]:
data[j], data[j + 1] = data[j + 1], data[j]
return data
class QuickSort(SortingStrategy):
def sort(self, data):
if len(data) <= 1:
return data
pivot = data[len(data) // 2]
left = [x for x in data if x < pivot]
middle = [x for x in data if x == pivot]
right = [x for x in data if x > pivot]
return self.sort(left) + middle + self.sort(right)
class SortContext:
def __init__(self, strategy):
self._strategy = strategy
def set_strategy(self, strategy):
self._strategy = strategy
def sort_data(self, data):
return self._strategy.sort(data)
# Usage
data = [64, 34, 25, 12, 22, 11, 90]
sorter = SortContext(BubbleSort())
print(sorter.sort_data(data)) # [11, 12, 22, 25, 34, 64, 90]
sorter.set_strategy(QuickSort())
print(sorter.sort_data(data)) # [11, 12, 22, 25, 34, 64, 90]
Advanced OOP Concepts
Metaclasses
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
def __init__(self):
self.connection = "Database connection"
def query(self, sql):
return f"Executing: {sql}"
# Usage
db1 = Database()
db2 = Database()
print(db1 is db2) # True
Composition vs Inheritance
# 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 # Composition: Car HAS-A Engine
def start(self):
return f"{self.brand} car: {self.engine.start()}"
# Usage
engine = Engine(200)
car = Car("Toyota", engine)
print(car.start()) # "Toyota car: Engine with 200 HP started"
Context Managers
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
print(f"Opening file: {self.filename}")
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_value, traceback):
print(f"Closing file: {self.filename}")
if self.file:
self.file.close()
if exc_type:
print(f"Exception occurred: {exc_type.__name__}: {exc_value}")
return False # Don't suppress exceptions
# Usage
with FileManager("test.txt", "w") as f:
f.write("Hello, World!")
# File is automatically closed
Quick Reference
Method Types Summary
class Example:
class_var = "shared"
def __init__(self, value):
self.instance_var = value
def instance_method(self):
# Access to self and class
return self.instance_var
@classmethod
def class_method(cls):
# Access to class, not instance
return cls.class_var
@staticmethod
def static_method():
# No access to class or instance
return "independent function"
@property
def computed_property(self):
# Computed property
return self.instance_var * 2
Common Magic Methods
class MyClass:
def __init__(self): # Constructor
pass
def __str__(self): # String representation
return "MyClass instance"
def __repr__(self): # Developer representation
return "MyClass()"
def __len__(self): # Length
return 0
def __getitem__(self, key): # Indexing
return None
def __setitem__(self, key, value): # Item assignment
pass
def __contains__(self, item): # 'in' operator
return False
def __iter__(self): # Iteration
return iter([])
def __call__(self): # Make instance callable
return "Called!"
def __enter__(self): # Context manager enter
return self
def __exit__(self, exc_type, exc_value, traceback): # Context manager exit
pass
Inheritance Best Practices
class Parent:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello from {self.name}"
class Child(Parent):
def __init__(self, name, age):
super().__init__(name) # Call parent constructor
self.age = age
def greet(self):
# Extend parent method
parent_greeting = super().greet()
return f"{parent_greeting}, I'm {self.age} years old"
This comprehensive cheatsheet covers all major Python OOP concepts with practical examples. Use it as a reference for building robust object-oriented applications.