This document provides a comprehensive guide to object-oriented programming in Python, including classes, objects, inheritance, encapsulation, polymorphism, and advanced OOP concepts with syntax and usage examples.
# Basic class definition
class Person:
"""A simple Person class"""
# Class variable (shared by all instances)
species = "Homo sapiens"
# Constructor method
def __init__(self, name, age):
# Instance variables (unique to each instance)
self.name = name
self.age = age
# Instance method
def introduce(self):
return f"Hi, I'm {self.name} and I'm {self.age} years old."
# Instance method with parameters
def have_birthday(self):
self.age += 1
return f"Happy birthday! {self.name} is now {self.age} years old."
# Creating objects (instances)
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
# Accessing attributes
print(person1.name) # Alice
print(person1.age) # 25
print(person1.species) # Homo sapiens
# Calling methods
print(person1.introduce()) # Hi, I'm Alice and I'm 25 years old.
print(person1.have_birthday()) # Happy birthday! Alice is now 26 years old.
# Class variables are shared
print(Person.species) # Homo sapiens
print(person1.species) # Homo sapiens
print(person2.species) # Homo sapiens
# Modifying class variable affects all instances
Person.species = "Human"
print(person1.species) # Human
print(person2.species) # Humanclass Counter:
# Class variable
total_count = 0
def __init__(self, name):
# Instance variable
self.name = name
self.count = 0
# Modify class variable
Counter.total_count += 1
def increment(self):
self.count += 1
def get_info(self):
return f"{self.name}: {self.count}, Total counters: {Counter.total_count}"
# Create instances
counter1 = Counter("Counter1")
counter2 = Counter("Counter2")
print(counter1.get_info()) # Counter1: 0, Total counters: 2
print(counter2.get_info()) # Counter2: 0, Total counters: 2
# Increment individual counters
counter1.increment()
counter1.increment()
counter2.increment()
print(counter1.get_info()) # Counter1: 2, Total counters: 2
print(counter2.get_info()) # Counter2: 1, Total counters: 2
# Access class variable directly
print(f"Total counters created: {Counter.total_count}") # Total counters created: 2class MathUtils:
pi = 3.14159
def __init__(self, value):
self.value = value
# Instance method - has access to self
def square(self):
"""Instance method - operates on instance data"""
return self.value ** 2
# Class method - has access to cls (class itself)
@classmethod
def from_string(cls, value_str):
"""Class method - alternative constructor"""
value = float(value_str)
return cls(value)
@classmethod
def get_pi(cls):
"""Class method - access class variables"""
return cls.pi
# Static method - no access to self or cls
@staticmethod
def add(a, b):
"""Static method - utility function"""
return a + b
@staticmethod
def is_even(number):
"""Static method - doesn't need class or instance data"""
return number % 2 == 0
# Using instance methods
math_obj = MathUtils(5)
print(math_obj.square()) # 25
# Using class methods
math_obj2 = MathUtils.from_string("7.5") # Alternative constructor
print(math_obj2.value) # 7.5
print(MathUtils.get_pi()) # 3.14159
# Using static methods
print(MathUtils.add(10, 20)) # 30
print(MathUtils.is_even(42)) # True
# Static methods can be called from instances too
print(math_obj.add(5, 10)) # 15class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
"""Getter for celsius"""
return self._celsius
@celsius.setter
def celsius(self, value):
"""Setter for celsius with validation"""
if value < -273.15:
raise ValueError("Temperature cannot be below absolute zero")
self._celsius = value
@celsius.deleter
def celsius(self):
"""Deleter for celsius"""
print("Deleting celsius value")
self._celsius = 0
@property
def fahrenheit(self):
"""Read-only property calculated from celsius"""
return (self._celsius * 9/5) + 32
@property
def kelvin(self):
"""Read-only property calculated from celsius"""
return self._celsius + 273.15
# Using properties
temp = Temperature(25)
# Get values
print(f"Celsius: {temp.celsius}") # Celsius: 25
print(f"Fahrenheit: {temp.fahrenheit}") # Fahrenheit: 77.0
print(f"Kelvin: {temp.kelvin}") # Kelvin: 298.15
# Set celsius (uses setter with validation)
temp.celsius = 30
print(f"New Celsius: {temp.celsius}") # New Celsius: 30
# Try invalid value
try:
temp.celsius = -300 # Below absolute zero
except ValueError as e:
print(f"Error: {e}")
# Delete property
del temp.celsius # Deleting celsius value
print(f"After deletion: {temp.celsius}") # After deletion: 0class BankAccount:
def __init__(self, account_number, initial_balance=0):
# Public attribute
self.account_number = account_number
# Protected attribute (convention: single underscore)
self._owner = None
# Private attribute (name mangling: double underscore)
self.__balance = initial_balance
# Private method
self.__validate_amount(initial_balance)
# Private method
def __validate_amount(self, amount):
if amount < 0:
raise ValueError("Amount cannot be negative")
# Protected method (convention)
def _log_transaction(self, transaction_type, amount):
print(f"Transaction: {transaction_type} ${amount}")
# Public methods to access private data
def deposit(self, amount):
self.__validate_amount(amount)
self.__balance += amount
self._log_transaction("DEPOSIT", amount)
def withdraw(self, amount):
self.__validate_amount(amount)
if amount > self.__balance:
raise ValueError("Insufficient funds")
self.__balance -= amount
self._log_transaction("WITHDRAW", amount)
def get_balance(self):
return self.__balance
# Property for protected attribute
@property
def owner(self):
return self._owner
@owner.setter
def owner(self, name):
self._owner = name
# Using the class
account = BankAccount("123456789", 1000)
# Public attribute access
print(f"Account Number: {account.account_number}") # Account Number: 123456789
# Protected attribute access (allowed but not recommended)
account._owner = "John Doe"
print(f"Owner: {account.owner}") # Owner: John Doe
# Public method access
account.deposit(500) # Transaction: DEPOSIT $500
print(f"Balance: ${account.get_balance()}") # Balance: $1500
# Try to access private attribute directly (won't work as expected)
# print(account.__balance) # AttributeError
# Private attributes are name-mangled
print(f"Private balance: ${account._BankAccount__balance}") # Private balance: $1500
# Protected method can be called (but shouldn't be)
account._log_transaction("TEST", 100) # Transaction: TEST $100
# Attempting to access private method
try:
account.__validate_amount(100)
except AttributeError as e:
print(f"Can't access private method: {e}")class AccessControlDemo:
def __init__(self):
# Public
self.public_var = "Anyone can access this"
# Protected (single underscore - convention)
self._protected_var = "Subclasses and internal use"
# Private (double underscore - name mangling)
self.__private_var = "Only this class can access"
def public_method(self):
"""Public method - anyone can call"""
return "This is a public method"
def _protected_method(self):
"""Protected method - internal use and subclasses"""
return "This is a protected method"
def __private_method(self):
"""Private method - only this class"""
return "This is a private method"
def access_all_methods(self):
"""Demonstrate access to all method types from within class"""
print(f"Public: {self.public_method()}")
print(f"Protected: {self._protected_method()}")
print(f"Private: {self.__private_method()}")
def show_all_attributes(self):
"""Show all attributes from within the class"""
print(f"Public: {self.public_var}")
print(f"Protected: {self._protected_var}")
print(f"Private: {self.__private_var}")
# Demonstrate access
demo = AccessControlDemo()
# Public access works
print(demo.public_var) # Anyone can access this
print(demo.public_method()) # This is a public method
# Protected access works but is discouraged
print(demo._protected_var) # Subclasses and internal use
print(demo._protected_method()) # This is a protected method
# Private access doesn't work directly
try:
print(demo.__private_var)
except AttributeError as e:
print(f"Private access failed: {e}")
# But private attributes are accessible via name mangling
print(demo._AccessControlDemo__private_var) # Only this class can access
# Access all from within the class
demo.access_all_methods()
demo.show_all_attributes()# Base class (Parent class)
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
self.is_alive = True
def make_sound(self):
return "Some generic animal sound"
def eat(self, food):
return f"{self.name} is eating {food}"
def sleep(self):
return f"{self.name} is sleeping"
def __str__(self):
return f"{self.name} the {self.species}"
# Derived class (Child class)
class Dog(Animal):
def __init__(self, name, breed):
# Call parent constructor
super().__init__(name, "Dog")
self.breed = breed
self.is_trained = False
# Override parent method
def make_sound(self):
return "Woof! Woof!"
# New method specific to Dog
def fetch(self, item):
return f"{self.name} is fetching the {item}"
def train(self):
self.is_trained = True
return f"{self.name} has been trained!"
# Another derived class
class Cat(Animal):
def __init__(self, name, indoor=True):
super().__init__(name, "Cat")
self.indoor = indoor
self.lives_remaining = 9
# Override parent method
def make_sound(self):
return "Meow!"
# New method specific to Cat
def climb(self):
return f"{self.name} is climbing"
def use_litter_box(self):
if self.indoor:
return f"{self.name} used the litter box"
return f"{self.name} went outside"
# Using inheritance
# Create instances
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers", indoor=True)
# Use inherited methods
print(dog.eat("dog food")) # Buddy is eating dog food
print(cat.sleep()) # Whiskers is sleeping
# Use overridden methods
print(dog.make_sound()) # Woof! Woof!
print(cat.make_sound()) # Meow!
# Use child-specific methods
print(dog.fetch("ball")) # Buddy is fetching the ball
print(cat.climb()) # Whiskers is climbing
# Check inheritance
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True
print(isinstance(cat, Dog)) # False
# Check class hierarchy
print(Dog.__mro__) # Method Resolution Order# Multiple base classes
class Flyable:
def __init__(self):
self.can_fly = True
self.altitude = 0
def take_off(self):
self.altitude = 100
return "Taking off!"
def land(self):
self.altitude = 0
return "Landing!"
class Swimmable:
def __init__(self):
self.can_swim = True
self.depth = 0
def dive(self):
self.depth = 10
return "Diving!"
def surface(self):
self.depth = 0
return "Surfacing!"
class Bird(Animal, Flyable):
def __init__(self, name, species, wing_span):
# Call both parent constructors
Animal.__init__(self, name, species)
Flyable.__init__(self)
self.wing_span = wing_span
def make_sound(self):
return "Tweet tweet!"
class Duck(Bird, Swimmable):
def __init__(self, name):
# Call all parent constructors
Bird.__init__(self, name, "Duck", "Medium")
Swimmable.__init__(self)
def make_sound(self):
return "Quack quack!"
def migrate(self):
return f"{self.name} is migrating south"
# Using multiple inheritance
duck = Duck("Donald")
# Methods from Animal
print(duck.eat("bread")) # Donald is eating bread
# Methods from Flyable
print(duck.take_off()) # Taking off!
# Methods from Swimmable
print(duck.dive()) # Diving!
# Duck-specific method
print(duck.migrate()) # Donald is migrating south
# Check inheritance
print(isinstance(duck, Duck)) # True
print(isinstance(duck, Bird)) # True
print(isinstance(duck, Animal)) # True
print(isinstance(duck, Flyable)) # True
print(isinstance(duck, Swimmable)) # True
# Method Resolution Order (MRO)
print(Duck.__mro__)class A:
def method(self):
print("Method from A")
def common_method(self):
print("A's common method")
class B(A):
def method(self):
print("Method from B")
super().method() # Call parent method
def common_method(self):
print("B's common method")
super().common_method()
class C(A):
def method(self):
print("Method from C")
super().method()
def common_method(self):
print("C's common method")
super().common_method()
class D(B, C):
def method(self):
print("Method from D")
super().method()
def common_method(self):
print("D's common method")
super().common_method()
# Demonstrate MRO
d = D()
print("MRO:", D.__mro__) # Shows method resolution order
print("\nCalling method():")
d.method()
# Output:
# Method from D
# Method from B
# Method from C
# Method from A
print("\nCalling common_method():")
d.common_method()
# Output:
# D's common method
# B's common method
# C's common method
# A's common method
# Cooperative inheritance example
class Shape:
def __init__(self, **kwargs):
self.name = kwargs.get('name', 'Unknown')
super().__init__(**kwargs) # Forward remaining kwargs
def info(self):
return f"Shape: {self.name}"
class Colorable:
def __init__(self, **kwargs):
self.color = kwargs.get('color', 'transparent')
super().__init__(**kwargs)
def info(self):
base_info = super().info() if hasattr(super(), 'info') else ""
return f"{base_info}, Color: {self.color}"
class Rectangle(Shape, Colorable):
def __init__(self, width, height, **kwargs):
self.width = width
self.height = height
super().__init__(**kwargs)
def area(self):
return self.width * self.height
def info(self):
base_info = super().info()
return f"{base_info}, Area: {self.area()}"
# Cooperative inheritance in action
rect = Rectangle(10, 5, name="Rectangle", color="blue")
print(rect.info()) # Shape: Rectangle, Color: blue, Area: 50# Base class defining interface
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def start_engine(self):
return "Engine started"
def stop_engine(self):
return "Engine stopped"
def accelerate(self):
raise NotImplementedError("Subclass must implement accelerate()")
def get_info(self):
return f"{self.brand} {self.model}"
# Different implementations
class Car(Vehicle):
def __init__(self, brand, model, doors):
super().__init__(brand, model)
self.doors = doors
def accelerate(self):
return "Car accelerating smoothly"
def honk(self):
return "Beep beep!"
class Motorcycle(Vehicle):
def __init__(self, brand, model, engine_size):
super().__init__(brand, model)
self.engine_size = engine_size
def accelerate(self):
return "Motorcycle accelerating quickly"
def wheelie(self):
return "Doing a wheelie!"
class Truck(Vehicle):
def __init__(self, brand, model, cargo_capacity):
super().__init__(brand, model)
self.cargo_capacity = cargo_capacity
def accelerate(self):
return "Truck accelerating slowly but powerfully"
def load_cargo(self, weight):
return f"Loading {weight} tons of cargo"
# Polymorphism in action
def test_vehicle(vehicle):
"""Function that works with any Vehicle type"""
print(f"Testing: {vehicle.get_info()}")
print(f"Start: {vehicle.start_engine()}")
print(f"Accelerate: {vehicle.accelerate()}") # Different for each type
print(f"Stop: {vehicle.stop_engine()}")
print("-" * 40)
# Create different vehicle instances
vehicles = [
Car("Toyota", "Camry", 4),
Motorcycle("Harley", "Davidson", 1200),
Truck("Ford", "F-150", 2.5)
]
# Polymorphic behavior
for vehicle in vehicles:
test_vehicle(vehicle) # Same function, different behavior
# Another example of polymorphism
def garage_service(vehicles):
"""Service all vehicles regardless of type"""
for vehicle in vehicles:
print(f"Servicing {vehicle.get_info()}")
if hasattr(vehicle, 'honk'):
print(f" Testing horn: {vehicle.honk()}")
if hasattr(vehicle, 'wheelie'):
print(f" Testing balance: {vehicle.wheelie()}")
if hasattr(vehicle, 'load_cargo'):
print(f" Testing cargo: {vehicle.load_cargo(1.0)}")
garage_service(vehicles)# Duck typing: "If it walks like a duck and quacks like a duck, it's a duck"
class Duck:
def quack(self):
return "Quack!"
def walk(self):
return "Waddle waddle"
class Person:
def quack(self):
return "I'm imitating a duck: Quack!"
def walk(self):
return "Walking on two legs"
class Robot:
def quack(self):
return "BEEP: Quack sound simulation"
def walk(self):
return "WHIRR: Mechanical walking motion"
# Function that uses duck typing
def make_it_quack_and_walk(thing):
"""This function works with anything that can quack and walk"""
print(f"Quacking: {thing.quack()}")
print(f"Walking: {thing.walk()}")
# Duck typing in action
duck = Duck()
person = Person()
robot = Robot()
# All work with the same function
make_it_quack_and_walk(duck) # Works with Duck
make_it_quack_and_walk(person) # Works with Person
make_it_quack_and_walk(robot) # Works with Robot
# Protocol-based example
class File:
def read(self):
return "Reading from file"
def write(self, data):
return f"Writing '{data}' to file"
class Network:
def read(self):
return "Reading from network"
def write(self, data):
return f"Sending '{data}' over network"
class StringIO:
def __init__(self):
self.data = ""
def read(self):
return f"Reading: {self.data}"
def write(self, data):
self.data += data
return f"Appended '{data}' to string buffer"
# Function that works with any "file-like" object
def process_data(stream, data):
"""Process data using any object with read/write methods"""
print(f"Before: {stream.read()}")
print(f"Operation: {stream.write(data)}")
print(f"After: {stream.read()}")
# All these work due to duck typing
file_obj = File()
network_obj = Network()
string_obj = StringIO()
process_data(file_obj, "test data")
process_data(network_obj, "network data")
process_data(string_obj, "buffer data")from abc import ABC, abstractmethod, abstractproperty
import math
# Abstract base class
class Shape(ABC):
"""Abstract base class for shapes"""
def __init__(self, name):
self.name = name
@abstractmethod
def area(self):
"""Calculate the area of the shape"""
pass
@abstractmethod
def perimeter(self):
"""Calculate the perimeter of the shape"""
pass
@abstractproperty
def description(self):
"""Description of the shape"""
pass
# Concrete method (can be used by subclasses)
def display_info(self):
return f"{self.description}: Area={self.area():.2f}, Perimeter={self.perimeter():.2f}"
# Concrete implementations
class Circle(Shape):
def __init__(self, radius):
super().__init__("Circle")
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
@property
def description(self):
return f"Circle with radius {self.radius}"
class Rectangle(Shape):
def __init__(self, width, height):
super().__init__("Rectangle")
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
@property
def description(self):
return f"Rectangle {self.width}x{self.height}"
class Triangle(Shape):
def __init__(self, a, b, c):
super().__init__("Triangle")
self.a, self.b, self.c = a, b, c
# Validate triangle inequality
if not (a + b > c and b + c > a and a + c > b):
raise ValueError("Invalid triangle sides")
def area(self):
# Heron's formula
s = self.perimeter() / 2
return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
def perimeter(self):
return self.a + self.b + self.c
@property
def description(self):
return f"Triangle with sides {self.a}, {self.b}, {self.c}"
# Using abstract base classes
shapes = [
Circle(5),
Rectangle(4, 6),
Triangle(3, 4, 5)
]
for shape in shapes:
print(shape.display_info())
# Cannot instantiate abstract class
try:
abstract_shape = Shape("test") # This will raise TypeError
except TypeError as e:
print(f"Cannot instantiate abstract class: {e}")
# Check if class is abstract
print(f"Is Shape abstract? {Shape.__abstractmethods__}")
print(f"Is Circle abstract? {Circle.__abstractmethods__}")from typing import Protocol, runtime_checkable
# Protocol definition (Python 3.8+)
@runtime_checkable
class Drawable(Protocol):
"""Protocol for drawable objects"""
def draw(self) -> str:
"""Draw the object"""
...
def get_bounds(self) -> tuple:
"""Get bounding box (x, y, width, height)"""
...
@runtime_checkable
class Serializable(Protocol):
"""Protocol for serializable objects"""
def serialize(self) -> dict:
"""Serialize object to dictionary"""
...
def deserialize(self, data: dict) -> None:
"""Deserialize from dictionary"""
...
# Classes implementing protocols
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
return f"Drawing point at ({self.x}, {self.y})"
def get_bounds(self):
return (self.x, self.y, 1, 1)
def serialize(self):
return {"x": self.x, "y": self.y}
def deserialize(self, data):
self.x = data["x"]
self.y = data["y"]
class Line:
def __init__(self, x1, y1, x2, y2):
self.x1, self.y1 = x1, y1
self.x2, self.y2 = x2, y2
def draw(self):
return f"Drawing line from ({self.x1}, {self.y1}) to ({self.x2}, {self.y2})"
def get_bounds(self):
x = min(self.x1, self.x2)
y = min(self.y1, self.y2)
w = abs(self.x2 - self.x1)
h = abs(self.y2 - self.y1)
return (x, y, w, h)
def serialize(self):
return {"x1": self.x1, "y1": self.y1, "x2": self.x2, "y2": self.y2}
def deserialize(self, data):
self.x1, self.y1 = data["x1"], data["y1"]
self.x2, self.y2 = data["x2"], data["y2"]
# Functions using protocols
def render_scene(drawables: list[Drawable]):
"""Render all drawable objects"""
for drawable in drawables:
print(drawable.draw())
bounds = drawable.get_bounds()
print(f" Bounds: {bounds}")
def save_objects(objects: list[Serializable]):
"""Save all serializable objects"""
data = []
for obj in objects:
data.append(obj.serialize())
return data
# Using protocols
point = Point(10, 20)
line = Line(0, 0, 100, 100)
# Check protocol compliance at runtime
print(f"Point implements Drawable: {isinstance(point, Drawable)}")
print(f"Line implements Serializable: {isinstance(line, Serializable)}")
# Use protocol-based functions
render_scene([point, line])
saved_data = save_objects([point, line])
print(f"Saved data: {saved_data}")class Vector:
"""A 2D vector class demonstrating magic methods"""
def __init__(self, x, y):
self.x = x
self.y = y
# String representation
def __str__(self):
"""Human-readable string representation"""
return f"Vector({self.x}, {self.y})"
def __repr__(self):
"""Developer-friendly string representation"""
return f"Vector(x={self.x}, y={self.y})"
# Arithmetic operations
def __add__(self, other):
"""Addition: v1 + v2"""
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __sub__(self, other):
"""Subtraction: v1 - v2"""
if isinstance(other, Vector):
return Vector(self.x - other.x, self.y - other.y)
return NotImplemented
def __mul__(self, scalar):
"""Scalar multiplication: v * scalar"""
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar):
"""Reverse scalar multiplication: scalar * v"""
return self.__mul__(scalar)
def __truediv__(self, scalar):
"""Division: v / scalar"""
if isinstance(scalar, (int, float)) and scalar != 0:
return Vector(self.x / scalar, self.y / scalar)
return NotImplemented
# Comparison operations
def __eq__(self, other):
"""Equality: v1 == v2"""
if isinstance(other, Vector):
return self.x == other.x and self.y == other.y
return False
def __ne__(self, other):
"""Inequality: v1 != v2"""
return not self.__eq__(other)
def __lt__(self, other):
"""Less than: v1 < v2 (comparing magnitudes)"""
if isinstance(other, Vector):
return self.magnitude() < other.magnitude()
return NotImplemented
def __le__(self, other):
"""Less than or equal: v1 <= v2"""
return self.__lt__(other) or self.__eq__(other)
def __gt__(self, other):
"""Greater than: v1 > v2"""
if isinstance(other, Vector):
return self.magnitude() > other.magnitude()
return NotImplemented
def __ge__(self, other):
"""Greater than or equal: v1 >= v2"""
return self.__gt__(other) or self.__eq__(other)
# Unary operations
def __neg__(self):
"""Negation: -v"""
return Vector(-self.x, -self.y)
def __pos__(self):
"""Positive: +v"""
return Vector(self.x, self.y)
def __abs__(self):
"""Absolute value: abs(v) - returns magnitude"""
return self.magnitude()
# Container-like behavior
def __len__(self):
"""Length: len(v) - number of components"""
return 2
def __getitem__(self, index):
"""Indexing: v[0] for x, v[1] for y"""
if index == 0:
return self.x
elif index == 1:
return self.y
else:
raise IndexError("Vector index out of range")
def __setitem__(self, index, value):
"""Item assignment: v[0] = value"""
if index == 0:
self.x = value
elif index == 1:
self.y = value
else:
raise IndexError("Vector index out of range")
def __iter__(self):
"""Iteration: for component in v"""
yield self.x
yield self.y
def __contains__(self, value):
"""Membership: value in v"""
return value == self.x or value == self.y
# Hash and boolean
def __hash__(self):
"""Hash: hash(v) - allows use in sets and as dict keys"""
return hash((self.x, self.y))
def __bool__(self):
"""Boolean: bool(v) - True if not zero vector"""
return self.x != 0 or self.y != 0
# Utility methods
def magnitude(self):
"""Calculate magnitude of vector"""
return (self.x ** 2 + self.y ** 2) ** 0.5
def normalize(self):
"""Return normalized vector"""
mag = self.magnitude()
if mag == 0:
return Vector(0, 0)
return Vector(self.x / mag, self.y / mag)
# Demonstrate magic methods
v1 = Vector(3, 4)
v2 = Vector(1, 2)
# String representation
print(f"str(v1): {str(v1)}") # str(v1): Vector(3, 4)
print(f"repr(v1): {repr(v1)}") # repr(v1): Vector(x=3, y=4)
# Arithmetic
v3 = v1 + v2 # Vector(4, 6)
v4 = v1 - v2 # Vector(2, 2)
v5 = v1 * 2 # Vector(6, 8)
v6 = 3 * v1 # Vector(9, 12)
v7 = v1 / 2 # Vector(1.5, 2.0)
print(f"v1 + v2 = {v3}")
print(f"v1 * 2 = {v5}")
# Comparison
print(f"v1 == v2: {v1 == v2}") # False
print(f"v1 > v2: {v1 > v2}") # True (magnitude comparison)
# Unary operations
print(f"-v1 = {-v1}") # Vector(-3, -4)
print(f"abs(v1) = {abs(v1)}") # 5.0
# Container operations
print(f"len(v1) = {len(v1)}") # 2
print(f"v1[0] = {v1[0]}") # 3
print(f"list(v1) = {list(v1)}") # [3, 4]
print(f"3 in v1: {3 in v1}") # True
# Hash and boolean
print(f"hash(v1) = {hash(v1)}")
print(f"bool(v1) = {bool(v1)}") # True
print(f"bool(Vector(0, 0)) = {bool(Vector(0, 0))}") # Falseclass FileManager:
"""Custom context manager for file operations"""
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
"""Enter the context"""
print(f"Opening file: {self.filename}")
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_value, traceback):
"""Exit the context"""
if self.file:
print(f"Closing file: {self.filename}")
self.file.close()
# Handle exceptions
if exc_type is not None:
print(f"Exception occurred: {exc_type.__name__}: {exc_value}")
return False # Don't suppress the exception
return True
# Using custom context manager
with FileManager('test.txt', 'w') as f:
f.write('Hello, World!')
# Database connection context manager
class DatabaseConnection:
def __init__(self, connection_string):
self.connection_string = connection_string
self.connection = None
self.transaction_active = False
def __enter__(self):
print(f"Connecting to database: {self.connection_string}")
# Simulate database connection
self.connection = f"Connected to {self.connection_string}"
self.transaction_active = True
return self
def __exit__(self, exc_type, exc_value, traceback):
if self.transaction_active:
if exc_type is None:
print("Committing transaction")
else:
print("Rolling back transaction due to exception")
if self.connection:
print("Closing database connection")
self.connection = None
return False # Don't suppress exceptions
def execute(self, query):
if not self.connection:
raise RuntimeError("Not connected to database")
return f"Executing: {query}"
# Using database context manager
try:
with DatabaseConnection("postgresql://localhost/mydb") as db:
result = db.execute("SELECT * FROM users")
print(result)
# Simulate an error
# raise ValueError("Something went wrong")
except Exception as e:
print(f"Caught exception: {e}")
# Timer context manager
import time
class Timer:
def __init__(self, name="Operation"):
self.name = name
self.start_time = None
def __enter__(self):
print(f"Starting {self.name}...")
self.start_time = time.time()
return self
def __exit__(self, exc_type, exc_value, traceback):
end_time = time.time()
duration = end_time - self.start_time
print(f"{self.name} completed in {duration:.4f} seconds")
return False
# Using timer context manager
with Timer("Data processing"):
time.sleep(1) # Simulate work
data = [i**2 for i in range(1000000)]class Validator:
"""Base descriptor for validation"""
def __init__(self, name=None):
self.name = name
def __set_name__(self, owner, name):
"""Called when descriptor is assigned to a class attribute"""
self.name = name
def __get__(self, obj, objtype=None):
"""Get attribute value"""
if obj is None:
return self
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
"""Set attribute value with validation"""
self.validate(value)
obj.__dict__[self.name] = value
def validate(self, value):
"""Override in subclasses"""
pass
class PositiveNumber(Validator):
"""Descriptor that validates positive numbers"""
def validate(self, value):
if not isinstance(value, (int, float)):
raise TypeError(f"{self.name} must be a number")
if value <= 0:
raise ValueError(f"{self.name} must be positive")
class NonEmptyString(Validator):
"""Descriptor that validates non-empty strings"""
def validate(self, value):
if not isinstance(value, str):
raise TypeError(f"{self.name} must be a string")
if not value.strip():
raise ValueError(f"{self.name} cannot be empty")
class RangeValidator(Validator):
"""Descriptor that validates values within a range"""
def __init__(self, min_val, max_val, name=None):
super().__init__(name)
self.min_val = min_val
self.max_val = max_val
def validate(self, value):
if not isinstance(value, (int, float)):
raise TypeError(f"{self.name} must be a number")
if not (self.min_val <= value <= self.max_val):
raise ValueError(f"{self.name} must be between {self.min_val} and {self.max_val}")
# Using descriptors
class Product:
name = NonEmptyString()
price = PositiveNumber()
rating = RangeValidator(1, 5)
def __init__(self, name, price, rating):
self.name = name
self.price = price
self.rating = rating
def __str__(self):
return f"{self.name}: ${self.price} (Rating: {self.rating}/5)"
# Test descriptor validation
try:
product = Product("Laptop", 999.99, 4)
print(product) # Works fine
# Test validations
product.price = -100 # Raises ValueError
except ValueError as e:
print(f"Validation error: {e}")
try:
product.name = "" # Raises ValueError
except ValueError as e:
print(f"Validation error: {e}")
try:
product.rating = 10 # Raises ValueError
except ValueError as e:
print(f"Validation error: {e}")class SingletonMeta(type):
"""Metaclass that creates singleton instances"""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class DatabaseConnection(metaclass=SingletonMeta):
"""Singleton database connection"""
def __init__(self, host="localhost"):
if hasattr(self, 'initialized'):
return
self.host = host
self.connected = False
self.initialized = True
print(f"Creating database connection to {host}")
def connect(self):
self.connected = True
print(f"Connected to {self.host}")
def disconnect(self):
self.connected = False
print(f"Disconnected from {self.host}")
# Test singleton behavior
db1 = DatabaseConnection("server1")
db2 = DatabaseConnection("server2") # Same instance as db1
print(f"db1 is db2: {db1 is db2}") # True
print(f"db1.host: {db1.host}") # server1 (first initialization)
print(f"db2.host: {db2.host}") # server1 (same instance)
# Validation metaclass
class ValidatedMeta(type):
"""Metaclass that adds validation to class creation"""
def __new__(mcs, name, bases, namespace):
# Ensure all methods have docstrings
for key, value in namespace.items():
if callable(value) and not key.startswith('_'):
if not getattr(value, '__doc__', None):
raise ValueError(f"Method {key} in class {name} must have a docstring")
# Add automatic string representation
if '__str__' not in namespace:
def auto_str(self):
attrs = ', '.join(f"{k}={v}" for k, v in self.__dict__.items())
return f"{name}({attrs})"
namespace['__str__'] = auto_str
return super().__new__(mcs, name, bases, namespace)
class Person(metaclass=ValidatedMeta):
"""A person class with validated methods"""
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
"""Greet someone"""
return f"Hello, I'm {self.name}"
def celebrate_birthday(self):
"""Celebrate birthday"""
self.age += 1
return f"Happy birthday! Now {self.age} years old"
# Test validated class
person = Person("Alice", 25)
print(person) # Uses auto-generated __str__
print(person.greet())
# Registry metaclass
class RegistryMeta(type):
"""Metaclass that maintains a registry of all classes"""
registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
mcs.registry[name] = cls
return cls
@classmethod
def get_class(mcs, name):
return mcs.registry.get(name)
@classmethod
def list_classes(mcs):
return list(mcs.registry.keys())
class Animal(metaclass=RegistryMeta):
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
# Test registry
print("Registered classes:", RegistryMeta.list_classes())
DogClass = RegistryMeta.get_class('Dog')
dog = DogClass()
print(f"Created instance: {type(dog)}")from dataclasses import dataclass, field, InitVar
from typing import List, ClassVar
import uuid
from datetime import datetime
# Basic dataclass
@dataclass
class Point:
x: float
y: float
def distance_from_origin(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
# Advanced dataclass features
@dataclass(frozen=True, order=True) # Immutable and comparable
class ImmutablePoint:
x: float
y: float
# Dataclass with default values and factories
@dataclass
class Person:
name: str
age: int = 0
hobbies: List[str] = field(default_factory=list) # Mutable default
id: str = field(default_factory=lambda: str(uuid.uuid4()))
created_at: datetime = field(default_factory=datetime.now)
# Class variable
species: ClassVar[str] = "Homo sapiens"
# Field not included in init
full_name: str = field(init=False)
def __post_init__(self):
"""Called after __init__"""
self.full_name = f"{self.name} (ID: {self.id[:8]})"
# Dataclass with InitVar
@dataclass
class Rectangle:
width: float
height: float
unit: InitVar[str] = "cm" # Not stored as instance variable
area: float = field(init=False)
perimeter: float = field(init=False)
description: str = field(init=False)
def __post_init__(self, unit):
self.area = self.width * self.height
self.perimeter = 2 * (self.width + self.height)
self.description = f"{self.width}x{self.height} {unit}"
# Custom field with validation
def validated_field(validator_func, **kwargs):
"""Create a field with validation"""
def validate_and_set(instance, value):
if not validator_func(value):
raise ValueError(f"Invalid value: {value}")
return value
return field(**kwargs)
@dataclass
class BankAccount:
account_number: str
balance: float = field(default=0.0)
def __post_init__(self):
if self.balance < 0:
raise ValueError("Balance cannot be negative")
def deposit(self, amount: float):
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self.balance += amount
def withdraw(self, amount: float):
if amount <= 0:
raise ValueError("Withdrawal amount must be positive")
if amount > self.balance:
raise ValueError("Insufficient funds")
self.balance -= amount
# Using dataclasses
point = Point(3.0, 4.0)
print(f"Point: {point}")
print(f"Distance from origin: {point.distance_from_origin()}")
# Frozen dataclass
immutable_point = ImmutablePoint(1.0, 2.0)
# immutable_point.x = 5.0 # This would raise an error
# Dataclass comparison (with order=True)
p1 = ImmutablePoint(1, 2)
p2 = ImmutablePoint(3, 4)
print(f"p1 < p2: {p1 < p2}") # Compares as tuple
# Complex dataclass
person = Person("Alice", 30, ["reading", "swimming"])
print(f"Person: {person}")
print(f"Full name: {person.full_name}")
# Dataclass with InitVar
rect = Rectangle(10, 5, "inches")
print(f"Rectangle: {rect.description}")
print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")
# Bank account with validation
account = BankAccount("12345", 1000.0)
account.deposit(500.0)
print(f"Account balance: ${account.balance}")
try:
account.withdraw(2000.0) # Should raise error
except ValueError as e:
print(f"Error: {e}")This document covers comprehensive object-oriented programming in Python including basic classes, inheritance, polymorphism, encapsulation, abstract base classes, special methods, descriptors, metaclasses, and modern features like dataclasses. For the most up-to-date information, refer to the official Python documentation.