Daily Tech Brief

Top startup stories in your inbox

Subscribe Free

© 2026 rakrisi Daily

Advanced OOP - Mastering Python Classes

Advanced OOP: Mastering Python Classes

Welcome to Advanced OOP! You’ve learned the fundamentals - now let’s explore advanced techniques that will make you an OOP master. We’ll cover class variables, static methods, method resolution order, and design patterns.

Class Variables vs Instance Variables

Understanding the Difference

class Employee:
    # Class variables (shared by all instances)
    company_name = "Tech Corp"
    total_employees = 0
    employee_ids = []

    def __init__(self, name, salary):
        # Instance variables (unique to each instance)
        self.name = name
        self.salary = salary
        self.id = len(Employee.employee_ids) + 1

        # Modify class variables
        Employee.total_employees += 1
        Employee.employee_ids.append(self.id)

    @classmethod
    def get_total_employees(cls):
        """Class method - works with class, not instance."""
        return cls.total_employees

    @staticmethod
    def is_valid_salary(salary):
        """Static method - utility function, no self or cls."""
        return isinstance(salary, (int, float)) and salary > 0

# Test class vs instance variables
emp1 = Employee("Alice", 50000)
emp2 = Employee("Bob", 60000)

print(f"Total employees: {Employee.get_total_employees()}")  # 2
print(f"Alice's company: {emp1.company_name}")              # "Tech Corp"
print(f"Bob's company: {emp2.company_name}")                # "Tech Corp"

# Changing class variable affects all instances
Employee.company_name = "Super Tech Corp"
print(f"Alice's company: {emp1.company_name}")              # "Super Tech Corp"

# Instance variables are independent
emp1.name = "Alice Johnson"
print(f"emp1 name: {emp1.name}")                            # "Alice Johnson"
print(f"emp2 name: {emp2.name}")                            # "Bob"

# Static method usage
print(f"Valid salary 50000: {Employee.is_valid_salary(50000)}")  # True
print(f"Valid salary -100: {Employee.is_valid_salary(-100)}")    # False

Class Methods and Static Methods

When to Use Each

class DateUtils:
    """Utility class for date operations."""

    def __init__(self, date_string):
        self.date_string = date_string

    @classmethod
    def from_timestamp(cls, timestamp):
        """Create instance from Unix timestamp."""
        import datetime
        dt = datetime.datetime.fromtimestamp(timestamp)
        date_string = dt.strftime("%Y-%m-%d")
        return cls(date_string)

    @classmethod
    def today(cls):
        """Create instance for today's date."""
        import datetime
        today = datetime.datetime.now().strftime("%Y-%m-%d")
        return cls(today)

    @staticmethod
    def is_valid_date(date_string):
        """Check if date string is valid (static method)."""
        import re
        pattern = r'^\d{4}-\d{2}-\d{2}$'
        if not re.match(pattern, date_string):
            return False

        # Additional validation could go here
        return True

    @staticmethod
    def days_between(date1, date2):
        """Calculate days between two dates (static method)."""
        import datetime
        d1 = datetime.datetime.strptime(date1, "%Y-%m-%d")
        d2 = datetime.datetime.strptime(date2, "%Y-%m-%d")
        return abs((d2 - d1).days)

# Test class and static methods
# Class methods
today_date = DateUtils.today()
timestamp_date = DateUtils.from_timestamp(1609459200)  # 2021-01-01

print(f"Today's date: {today_date.date_string}")
print(f"From timestamp: {timestamp_date.date_string}")

# Static methods
print(f"Is '2021-01-01' valid? {DateUtils.is_valid_date('2021-01-01')}")
print(f"Is 'invalid' valid? {DateUtils.is_valid_date('invalid')}")
print(f"Days between: {DateUtils.days_between('2021-01-01', '2021-01-05')}")

Method Resolution Order (MRO)

Understanding Multiple Inheritance

class A:
    def method(self):
        return "Method from A"

class B(A):
    def method(self):
        return "Method from B"

class C(A):
    def method(self):
        return "Method from C"

class D(B, C):
    pass

# Method Resolution Order
print(f"MRO for D: {D.__mro__}")
# Output: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

d = D()
print(f"d.method(): {d.method()}")  # "Method from B" (B comes first)

# Check MRO manually
print(f"D.__bases__: {D.__bases__}")  # (<class '__main__.B'>, <class '__main__.C'>)

Complex Multiple Inheritance

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "Some animal sound"

class Walker:
    def move(self):
        return "Walking"

class Swimmer:
    def move(self):
        return "Swimming"

class Flyer:
    def move(self):
        return "Flying"

class Duck(Animal, Walker, Swimmer, Flyer):
    def speak(self):
        return "Quack!"

# Test multiple inheritance
duck = Duck("Donald")

print(f"Name: {duck.name}")
print(f"Speak: {duck.speak()}")

# Method Resolution Order determines which move() is used
print(f"Move: {duck.move()}")  # "Walking" (Walker comes first after Animal)

# Can access all methods through class
print(f"Walk: {Walker.move(duck)}")
print(f"Swim: {Swimmer.move(duck)}")
print(f"Fly: {Flyer.move(duck)}")

print(f"MRO: {Duck.__mro__}")

Composition vs Inheritance

When to Use Composition

# Inheritance approach (not always best)
class Engine:
    def start(self):
        return "Engine started"

class Car(Engine):  # Car IS-A Engine?
    def __init__(self, make, model):
        self.make = make
        self.model = model

    def drive(self):
        engine_status = self.start()  # Inheriting start() method
        return f"{self.make} {self.model} is driving. {engine_status}"

# Composition approach (better)
class Engine:
    def start(self):
        return "Engine started"

    def stop(self):
        return "Engine stopped"

class Car:
    def __init__(self, make, model):
        self.make = make
        self.model = model
        self.engine = Engine()  # Car HAS-A Engine

    def drive(self):
        engine_status = self.engine.start()
        return f"{self.make} {self.model} is driving. {engine_status}"

    def park(self):
        engine_status = self.engine.stop()
        return f"{self.make} {self.model} is parked. {engine_status}"

# Test composition
car = Car("Toyota", "Camry")
print(car.drive())
print(car.park())

Composition Example: Computer System

class CPU:
    def __init__(self, cores, speed):
        self.cores = cores
        self.speed = speed

    def process(self, task):
        return f"CPU ({self.cores} cores @ {self.speed}GHz) processing: {task}"

class RAM:
    def __init__(self, size_gb):
        self.size_gb = size_gb

    def allocate(self, amount):
        if amount > self.size_gb:
            raise MemoryError("Not enough RAM")
        return f"Allocated {amount}GB RAM"

class Storage:
    def __init__(self, type_, capacity_gb):
        self.type = type_
        self.capacity_gb = capacity_gb

    def read(self, file_path):
        return f"Reading {file_path} from {self.type} storage"

    def write(self, file_path, data):
        return f"Writing {len(data)} bytes to {file_path} on {self.type} storage"

class Computer:
    def __init__(self, cpu_cores, cpu_speed, ram_gb, storage_type, storage_gb):
        self.cpu = CPU(cpu_cores, cpu_speed)
        self.ram = RAM(ram_gb)
        self.storage = Storage(storage_type, storage_gb)

    def run_program(self, program_name):
        # Use composition to delegate tasks
        cpu_result = self.cpu.process(f"Running {program_name}")
        ram_result = self.ram.allocate(2)  # Assume program needs 2GB
        storage_result = self.storage.read(f"{program_name}.exe")

        return f"{cpu_result}\n{ram_result}\n{storage_result}"

# Test composition
computer = Computer(cpu_cores=8, cpu_speed=3.5, ram_gb=16,
                   storage_type="SSD", storage_gb=512)

print(computer.run_program("Python"))

Abstract Base Classes (ABC)

Enforcing Interfaces

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    def __init__(self, amount):
        self.amount = amount

    @abstractmethod
    def process_payment(self):
        """Must be implemented by subclasses."""
        pass

    @abstractmethod
    def validate(self):
        """Must be implemented by subclasses."""
        pass

    def get_receipt(self):
        """Concrete method that can be inherited."""
        return f"Payment of ${self.amount} processed successfully"

class CreditCardProcessor(PaymentProcessor):
    def __init__(self, amount, card_number):
        super().__init__(amount)
        self.card_number = card_number

    def validate(self):
        if len(self.card_number.replace(" ", "")) < 13:
            raise ValueError("Invalid card number")
        return True

    def process_payment(self):
        self.validate()
        last_four = self.card_number[-4:]
        return f"Charged ${self.amount} to card ending in {last_four}"

class PayPalProcessor(PaymentProcessor):
    def __init__(self, amount, email):
        super().__init__(amount)
        self.email = email

    def validate(self):
        if "@" not in self.email:
            raise ValueError("Invalid PayPal email")
        return True

    def process_payment(self):
        self.validate()
        return f"Charged ${self.amount} via PayPal account {self.email}"

# Test ABC
processors = [
    CreditCardProcessor(100, "4111111111111111"),
    PayPalProcessor(50, "user@example.com")
]

for processor in processors:
    try:
        result = processor.process_payment()
        receipt = processor.get_receipt()
        print(f"{result}\n{receipt}\n")
    except ValueError as e:
        print(f"Validation error: {e}\n")

# This would fail - can't instantiate abstract class
# processor = PaymentProcessor(100)  # TypeError

Data Classes

Simplified Class Creation

from dataclasses import dataclass, field
from typing import List

@dataclass
class Person:
    name: str
    age: int
    email: str = ""  # Default value

    def greet(self):
        return f"Hello, I'm {self.name}"

@dataclass
class Address:
    street: str
    city: str
    zip_code: str

@dataclass
class Employee:
    name: str
    age: int
    salary: float
    address: Address
    skills: List[str] = field(default_factory=list)  # Mutable default

    def give_raise(self, percentage):
        self.salary *= (1 + percentage / 100)

    def add_skill(self, skill):
        if skill not in self.skills:
            self.skills.append(skill)

# Test data classes
person = Person("Alice", 30, "alice@example.com")
print(person)  # Person(name='Alice', age=30, email='alice@example.com')
print(person.greet())

address = Address("123 Main St", "Anytown", "12345")
employee = Employee("Bob", 25, 50000, address, ["Python", "Java"])

print(f"\nEmployee: {employee}")
employee.give_raise(10)
employee.add_skill("JavaScript")
print(f"After raise and skill: {employee}")

Properties with Advanced Features

Advanced Property Usage

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Temperature below absolute zero!")
        self._celsius = value

    @property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        self.celsius = (value - 32) * 5/9  # Uses celsius setter for validation

    @property
    def kelvin(self):
        return self._celsius + 273.15

    @kelvin.setter
    def kelvin(self, value):
        self.celsius = value - 273.15

# Test advanced properties
temp = Temperature(20)

print(f"Celsius: {temp.celsius}")
print(f"Fahrenheit: {temp.fahrenheit}")
print(f"Kelvin: {temp.kelvin}")

temp.fahrenheit = 100
print(f"After setting Fahrenheit to 100: {temp.celsius}°C")

try:
    temp.kelvin = -100  # Below absolute zero
except ValueError as e:
    print(f"Error: {e}")

Metaclasses

Classes That Create Classes

class SingletonMeta(type):
    """Metaclass that creates singleton classes."""
    _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):
    def __init__(self, host, port):
        self.host = host
        self.port = port
        print(f"Connecting to {host}:{port}")

# Test singleton
db1 = DatabaseConnection("localhost", 5432)
db2 = DatabaseConnection("localhost", 5432)  # Same instance

print(f"db1 is db2: {db1 is db2}")  # True
print(f"Same host: {db1.host == db2.host}")  # True

Practical Examples

Example 1: Plugin System

from abc import ABC, abstractmethod
import importlib
import os

class Plugin(ABC):
    @abstractmethod
    def execute(self, data):
        pass

    @property
    @abstractmethod
    def name(self):
        pass

class UppercasePlugin(Plugin):
    name = "uppercase"

    def execute(self, data):
        return data.upper()

class ReversePlugin(Plugin):
    name = "reverse"

    def execute(self, data):
        return data[::-1]

class PluginManager:
    def __init__(self):
        self.plugins = {}

    def register_plugin(self, plugin_class):
        if not issubclass(plugin_class, Plugin):
            raise TypeError("Plugin must inherit from Plugin class")

        plugin_instance = plugin_class()
        self.plugins[plugin_instance.name] = plugin_instance

    def execute_plugin(self, plugin_name, data):
        if plugin_name not in self.plugins:
            raise ValueError(f"Plugin '{plugin_name}' not found")

        return self.plugins[plugin_name].execute(data)

    def list_plugins(self):
        return list(self.plugins.keys())

# Test plugin system
manager = PluginManager()
manager.register_plugin(UppercasePlugin)
manager.register_plugin(ReversePlugin)

print(f"Available plugins: {manager.list_plugins()}")

text = "Hello World"
print(f"Original: {text}")
print(f"Uppercase: {manager.execute_plugin('uppercase', text)}")
print(f"Reverse: {manager.execute_plugin('reverse', text)}")

Example 2: Observer Pattern

from typing import List, Callable

class Subject:
    """Observable subject."""
    def __init__(self):
        self._observers: List[Callable] = []

    def attach(self, observer: Callable):
        """Attach an observer."""
        self._observers.append(observer)

    def detach(self, observer: Callable):
        """Detach an observer."""
        self._observers.remove(observer)

    def notify(self, event_data=None):
        """Notify all observers."""
        for observer in self._observers:
            observer(event_data)

class NewsPublisher(Subject):
    def __init__(self):
        super().__init__()
        self._news = []

    def publish_news(self, news_item):
        self._news.append(news_item)
        print(f"Publishing news: {news_item}")
        self.notify(news_item)

class EmailSubscriber:
    def __init__(self, name, email):
        self.name = name
        self.email = email

    def update(self, news):
        print(f"Email to {self.email}: New news - {news}")

class SMSSubscriber:
    def __init__(self, name, phone):
        self.name = name
        self.phone = phone

    def update(self, news):
        print(f"SMS to {self.phone}: {news[:50]}...")

# Test observer pattern
publisher = NewsPublisher()

# Create subscribers
email_sub = EmailSubscriber("Alice", "alice@example.com")
sms_sub = SMSSubscriber("Bob", "555-0123")

# Attach observers
publisher.attach(email_sub.update)
publisher.attach(sms_sub.update)

# Publish news
publisher.publish_news("Breaking: Python 4.0 released!")
publisher.publish_news("Tech stocks surge after AI breakthrough")

# Detach one observer
publisher.detach(sms_sub.update)

publisher.publish_news("Weather: Sunny skies expected")

Example 3: Factory Pattern

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

    @abstractmethod
    def move(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

    def move(self):
        return "Runs on four legs"

class Cat(Animal):
    def speak(self):
        return "Meow!"

    def move(self):
        return "Walks gracefully"

class Bird(Animal):
    def speak(self):
        return "Tweet!"

    def move(self):
        return "Flies through the air"

class AnimalFactory:
    """Factory for creating animals."""
    _animal_classes = {
        "dog": Dog,
        "cat": Cat,
        "bird": Bird
    }

    @classmethod
    def create_animal(cls, animal_type, **kwargs):
        """Create an animal of the specified type."""
        animal_type = animal_type.lower()
        if animal_type not in cls._animal_classes:
            raise ValueError(f"Unknown animal type: {animal_type}")

        animal_class = cls._animal_classes[animal_type]
        return animal_class(**kwargs)

    @classmethod
    def register_animal(cls, animal_type, animal_class):
        """Register a new animal type."""
        if not issubclass(animal_class, Animal):
            raise TypeError("Animal class must inherit from Animal")
        cls._animal_classes[animal_type.lower()] = animal_class

    @classmethod
    def get_available_types(cls):
        """Get list of available animal types."""
        return list(cls._animal_classes.keys())

# Test factory pattern
animals = []
for animal_type in ["dog", "cat", "bird"]:
    animal = AnimalFactory.create_animal(animal_type)
    animals.append(animal)
    print(f"{animal_type.capitalize()}: {animal.speak()}, {animal.move()}")

print(f"\nAvailable types: {AnimalFactory.get_available_types()}")

# Register a new animal type
class Fish(Animal):
    def speak(self):
        return "Blub!"

    def move(self):
        return "Swims in water"

AnimalFactory.register_animal("fish", Fish)
fish = AnimalFactory.create_animal("fish")
print(f"Fish: {fish.speak()}, {fish.move()}")

Practice Exercises

Exercise 1: Configuration Manager

Create a ConfigManager class with:

  • Class methods for loading/saving configuration
  • Instance methods for getting/setting values
  • Validation of configuration values
  • Singleton pattern to ensure only one instance

Exercise 2: Event System

Build an event system with:

  • Event base class with subclasses for different event types
  • EventDispatcher class for managing subscribers
  • Decorator for marking event handler methods
  • Support for both sync and async event handlers

Exercise 3: Database ORM

Create a simple ORM system with:

  • Model base class with metaclass for field management
  • Field classes (CharField, IntegerField, etc.)
  • Query methods (save(), delete(), find())
  • Relationship support (ForeignKey)

Exercise 4: Plugin Architecture

Build a plugin architecture with:

  • Abstract Plugin base class
  • PluginManager for loading/unloading plugins
  • Hook system for extending functionality
  • Dependency management between plugins

Exercise 5: State Machine

Create a state machine framework with:

  • StateMachine class with state transitions
  • State base class for implementing state behavior
  • Guard conditions for transitions
  • Entry/exit actions for states

Summary

Advanced OOP techniques take your classes to the next level:

Class vs Instance Variables:

class MyClass:
    class_var = "shared"  # Class variable

    def __init__(self):
        self.instance_var = "unique"  # Instance variable

Class Methods & Static Methods:

@classmethod
def class_method(cls): pass

@staticmethod
def static_method(): pass

Method Resolution Order:

class D(B, C): pass
print(D.__mro__)  # Shows inheritance order

Composition over Inheritance:

class Car:
    def __init__(self):
        self.engine = Engine()  # HAS-A relationship

Abstract Base Classes:

from abc import ABC, abstractmethod

class MyABC(ABC):
    @abstractmethod
    def required_method(self): pass

Data Classes:

@dataclass
class Person:
    name: str
    age: int

Key Advanced Concepts:

  • Class variables for shared state
  • Class/static methods for utility functions
  • MRO for multiple inheritance resolution
  • Composition for flexible object relationships
  • ABCs for interface contracts
  • Data classes for simple data containers
  • Metaclasses for class customization

Design Patterns:

  • Singleton (one instance)
  • Factory (object creation)
  • Observer (event handling)
  • Strategy (algorithm selection)

Next: Modules and Packages - organizing your code! 📦