Skip to content

OOP Cheatsheet

Quick reference for Object-Oriented Programming in Python: classes, inheritance, polymorphism, magic methods, and SOLID principles.


Classes & Objects

Defining a Class

class Dog:
    """A simple dog class."""

    # Class attribute (shared by all instances)
    species = "Canis familiaris"

    def __init__(self, name, age):
        """Initialize instance attributes."""
        self.name = name      # Instance attribute
        self.age = age        # Instance attribute

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

    @classmethod
    def from_birth_year(cls, name, birth_year):
        """Class method - alternative constructor."""
        age = 2024 - birth_year
        return cls(name, age)

    @staticmethod
    def is_valid_age(age):
        """Static method - no self/cls."""
        return 0 <= age <= 30

Creating & Using Objects

# Create instance
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)

# Access attributes
print(dog1.name)        # "Buddy"
print(dog1.species)     # "Canis familiaris" (class attribute)

# Call methods
print(dog1.bark())      # "Buddy says Woof!"

# Class method
dog3 = Dog.from_birth_year("Luna", 2018)

# Static method
print(Dog.is_valid_age(3))  # True

Encapsulation

Public, Protected, Private

class Account:
    def __init__(self, balance):
        self.balance = balance           # Public attribute
        self._pin = "1234"              # Protected (convention)
        self.__password = "secret"      # Private (name mangling)

    def get_balance(self):
        """Getter method."""
        return self.balance

    def set_balance(self, balance):
        """Setter method with validation."""
        if balance >= 0:
            self.balance = balance

    @property
    def balance(self):
        """Property - accessed like an attribute."""
        return self._balance

    @balance.setter
    def balance(self, value):
        """Property setter."""
        if value >= 0:
            self._balance = value
        else:
            raise ValueError("Balance cannot be negative")

Inheritance

Basic Inheritance

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

    def speak(self):
        pass

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

    def speak(self):
        return "Woof!"

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

# Usage
dog = Dog("Buddy", "Labrador")
print(dog.name)         # "Buddy"
print(dog.speak())       # "Woof!"

Multiple Inheritance

class Flyable:
    def fly(self):
        return "Flying"

class Swimmable:
    def swim(self):
        return "Swimming"

class Duck(Animal, Flyable, Swimmable):
    def speak(self):
        return "Quack!"

# Method Resolution Order (MRO)
print(Duck.mro())
# [<class 'Duck'>, <class 'Animal'>, <class 'Flyable'>,
#  <class 'Swimmable'>, <class 'object'>]

Abstract Base Classes

from abc import ABC, abstractmethod

class Shape(ABC):
    """Abstract base class."""

    @abstractmethod
    def area(self):
        """Abstract method - must be implemented."""
        pass

    @abstractmethod
    def perimeter(self):
        """Abstract method - must be implemented."""
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

# Cannot instantiate abstract class
# shape = Shape()  # TypeError

rect = Rectangle(5, 3)
print(rect.area())      # 15

Polymorphism

Method Overriding

class Animal:
    def speak(self):
        return "Some sound"

class Dog(Animal):
    def speak(self):
        return "Woof!"      # Override parent method

class Cat(Animal):
    def speak(self):
        return "Meow!"      # Override parent method

# Polymorphic usage
animals = [Dog("Buddy"), Cat("Whiskers")]
for animal in animals:
    print(animal.speak())  # "Woof!", "Meow!"

Duck Typing

class Duck:
    def quack(self):
        return "Quack!"

class Person:
    def quack(self):
        return "I'm quacking like a duck!"

def make_quack(obj):
    """Function works with any object with quack()."""
    return obj.quack()

# Both work - duck typing
make_quack(Duck())      # "Quack!"
make_quack(Person())    # "I'm quacking like a duck!"

Magic (Dunder) Methods

String Representation

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        """User-friendly string representation."""
        return f"{self.name} ({self.age})"

    def __repr__(self):
        """Developer-friendly representation."""
        return f"Person(name='{self.name}', age={self.age})"

person = Person("Alice", 25)
print(person)           # "Alice (25)" - uses __str__
print(repr(person))     # "Person(name='Alice', age=25)" - uses __repr__

Comparison Operators

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

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

    def __lt__(self, other):
        return (self.x, self.y) < (other.x, other.y)

    def __le__(self, other):
        return self <= other

    def __gt__(self, other):
        return self > other

    def __ge__(self, other):
        return self >= other

p1 = Point(1, 2)
p2 = Point(3, 4)

p1 < p2                 # True
p1 == p1                # True

Arithmetic Operators

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

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

    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

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

v1 = Vector(2, 3)
v2 = Vector(4, 5)

print(v1 + v2)          # Vector(6, 8)
print(v1 * 2)           # Vector(4, 6)

Container Methods

class MyList:
    def __init__(self, items=None):
        self.items = items or []

    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 __iter__(self):
        return iter(self.items)

    def __contains__(self, item):
        return item in self.items

# Usage
my_list = MyList([1, 2, 3])
print(len(my_list))     # 3
print(my_list[0])       # 1
my_list[0] = 10         # Modify
2 in my_list            # True
for item in my_list:
    print(item)         # Iteration

Callable Objects

class Counter:
    def __init__(self, start=0):
        self.count = start

    def __call__(self):
        self.count += 1
        return self.count

counter = Counter(5)
print(counter())        # 6
print(counter())        # 7

Decorators for Classes

@property

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

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

    @radius.setter
    def radius(self, value):
        """Setter with validation."""
        if value > 0:
            self._radius = value
        else:
            raise ValueError("Radius must be positive")

    @property
    def area(self):
        """Computed property (read-only)."""
        return 3.14 * self._radius ** 2

circle = Circle(5)
print(circle.radius)     # 5
print(circle.area)       # 78.5
circle.radius = 10      # Valid

@staticmethod & @classmethod

class MathUtils:
    @staticmethod
    def add(a, b):
        """No access to class or instance."""
        return a + b

    @classmethod
    def from_string(cls, s):
        """Alternative constructor."""
        parts = s.split()
        return cls(int(parts[0]), int(parts[1]))

result = MathUtils.add(1, 2)    # 3

Dataclasses

from dataclasses import dataclass, field
from typing import List

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

    def __post_init__(self):
        """Called after initialization."""
        if self.age < 18:
            raise ValueError("Employee must be 18+")

# Automatically gets __init__, __repr__, __eq__
emp = Employee("Alice", 25, 50000, ["Python", "SQL"])
print(emp)  # Employee(name='Alice', age=25, salary=50000, skills=['Python', 'SQL'])

SOLID Principles

S - Single Responsibility

# Bad: Multiple responsibilities
class User:
    def __init__(self, name):
        self.name = name

    def save_to_db(self):
        pass  # Database logic mixed with user logic

# Good: Separated concerns
class User:
    def __init__(self, name):
        self.name = name

class UserRepository:
    def save(self, user):
        pass  # Database logic

O - Open/Closed Principle

# Bad: Must modify to add new shape
class AreaCalculator:
    def calculate(self, shape):
        if shape.type == "rectangle":
            return shape.width * shape.height
        elif shape.type == "circle":
            return 3.14 * shape.radius ** 2

# Good: Open for extension, closed for modification
class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class AreaCalculator:
    def calculate(self, shape):
        return shape.area()  # Works with any Shape

L - Liskov Substitution Principle

# Bad: Violates LSP
class Bird:
    def fly(self):
        pass

class Penguin(Bird):
    def fly(self):
        raise Exception("Penguins can't fly!")

# Good: Separate behaviors
class Bird:
    pass

class FlyingBird(Bird):
    def fly(self):
        pass

class Penguin(Bird):
    def swim(self):
        pass

I - Interface Segregation Principle

# Bad: Large interface
class Worker(ABC):
    @abstractmethod
    def work(self):
        pass

    @abstractmethod
    def eat(self):
        pass

# Good: Segregated interfaces
class Workable(ABC):
    @abstractmethod
    def work(self):
        pass

class Eatable(ABC):
    @abstractmethod
    def eat(self):
        pass

D - Dependency Inversion Principle

# Bad: High-level depends on low-level
class LightSwitch:
    def __init__(self):
        self.bulb = LightBulb()

    def toggle(self):
        self.bulb.turn_on()

# Good: Depends on abstraction
class Switchable(ABC):
    @abstractmethod
    def turn_on(self):
        pass

class LightSwitch:
    def __init__(self, device: Switchable):
        self.device = device

    def toggle(self):
        self.device.turn_on()

Common Patterns

Singleton

class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

s1 = Singleton()
s2 = Singleton()
print(s1 is s2)  # True

Factory

class AnimalFactory:
    @staticmethod
    def create_animal(animal_type):
        animals = {
            "dog": Dog,
            "cat": Cat,
            "bird": Bird
        }
        return animals.get(animal_type)()

Observer

class Subject:
    def __init__(self):
        self._observers = []

    def attach(self, observer):
        self._observers.append(observer)

    def notify(self):
        for observer in self._observers:
            observer.update(self)

Quick Reference

Concept Syntax Purpose
Class class Name: Define class
Init def __init__(self): Constructor
Property @property Computed attribute
Static Method @staticmethod Class utility
Class Method @classmethod Alternative constructor
Abstract @abstractmethod Force implementation
Inheritance class Child(Parent): Extend class
Super super().__init__() Call parent method

Back to Resources