Advanced

Object-Oriented Programming

Learn to design classes, use inheritance and polymorphism, encapsulate data, and leverage Python's special methods and properties.

Classes and Objects

A class is a blueprint for creating objects. An object is an instance of a class.

Python
class Dog:
    """A simple Dog class."""

    species = "Canis familiaris"  # Class attribute

    def __init__(self, name, age):
        """Initialize a new Dog."""
        self.name = name    # Instance attribute
        self.age = age      # Instance attribute

    def bark(self):
        return f"{self.name} says Woof!"

    def info(self):
        return f"{self.name} is {self.age} years old"

# Create objects (instances)
buddy = Dog("Buddy", 3)
max_dog = Dog("Max", 5)

print(buddy.bark())    # Buddy says Woof!
print(max_dog.info())  # Max is 5 years old
print(Dog.species)     # Canis familiaris

Class Methods and Static Methods

Python
class Employee:
    raise_rate = 1.04
    count = 0

    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
        Employee.count += 1

    # Instance method
    def apply_raise(self):
        self.salary *= self.raise_rate

    # Class method - works with the class, not instances
    @classmethod
    def set_raise_rate(cls, rate):
        cls.raise_rate = rate

    # Class method as alternative constructor
    @classmethod
    def from_string(cls, emp_str):
        name, salary = emp_str.split("-")
        return cls(name, int(salary))

    # Static method - utility, no access to cls or self
    @staticmethod
    def is_workday(day):
        return day.weekday() < 5

emp = Employee.from_string("Alice-75000")

Inheritance

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

    def speak(self):
        raise NotImplementedError("Subclass must implement")

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # Call parent's __init__
        self.breed = breed

    def speak(self):
        return f"{self.name} says Woof!"

cat = Cat("Whiskers")
dog = Dog("Rex", "Labrador")
print(cat.speak())  # Whiskers says Meow!
print(dog.speak())  # Rex says Woof!

Polymorphism

Python
# Same interface, different behavior
animals = [Cat("Whiskers"), Dog("Rex", "Lab"), Cat("Tom")]

for animal in animals:
    print(animal.speak())  # Each calls its own version

Encapsulation

Python
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner          # Public
        self._balance = balance     # Protected (convention)
        self.__pin = "1234"        # Private (name mangling)

    def deposit(self, amount):
        if amount > 0:
            self._balance += amount
            return True
        return False

    def get_balance(self):
        return self._balance

acct = BankAccount("Alice", 1000)
acct.deposit(500)
print(acct.get_balance())  # 1500

Magic Methods (Dunder Methods)

Python
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    def __str__(self):
        return f"({self.x}, {self.y})"

    def __len__(self):
        return int((self.x**2 + self.y**2)**0.5)

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2)    # (4, 6)
print(repr(v1))   # Vector(3, 4)
print(len(v1))    # 5

Properties (@property)

Python
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        """Get the radius."""
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

    @property
    def area(self):
        """Computed property."""
        import math
        return math.pi * self._radius ** 2

c = Circle(5)
print(c.radius)     # 5 (uses getter)
print(c.area)       # 78.54 (computed)
c.radius = 10       # Uses setter with validation
When to use OOP: Use classes when you have data and behavior that belong together, need multiple instances with shared behavior, or want to model real-world entities. For simple scripts, functions alone are often sufficient.