Object-Oriented Programming (OOP) - Complete Reference Guide
Quick Reference Card
| Concept | Syntax | Purpose |
|---|---|---|
| Define class | class MyClass: |
Create new class |
| Constructor | def __init__(self): |
Initialize instance |
| Instance attribute | self.attr = value |
Unique to instance |
| Class attribute | MyClass.attr = value |
Shared by all |
| Instance method | def method(self): |
Operate on instance |
| Class method | @classmethod |
Operate on class |
| Static method | @staticmethod |
Independent utility |
| Inheritance | class Child(Parent): |
Extend parent class |
| Call parent | super().__init__() |
Access parent |
| Property | @property |
Getter method |
| Setter | @attr.setter |
Setter method |
| String repr | def __str__(self): |
String conversion |
| Equality | def __eq__(self, other): |
== operator |
Table of Contents
- Classes & Objects
- Attributes & Methods
- Constructors & Initialization
- The Four Pillars of OOP
- Special Methods (Magic Methods)
- OOP Design Principles (SOLID)
Classes & Objects
Basic Class Definition
# Define a class
class Dog:
pass
# Create an object (instance)
my_dog = Dog()
Class with Attributes
class Dog:
# Class attribute (shared by all instances)
species = "Canis familiaris"
def __init__(self, name, age):
# Instance attributes (unique to each instance)
self.name = name
self.age = age
# Create instances
buddy = Dog("Buddy", 3)
charlie = Dog("Charlie", 5)
# Access attributes
print(buddy.name) # "Buddy"
print(buddy.species) # "Canis familiaris"
print(charlie.age) # 5
What is self?
# self refers to the instance itself
class Person:
def __init__(self, name):
self.name = name # self.name is instance attribute
def greet(self):
# self allows access to instance attributes
return f"Hello, my name is {self.name}"
Wilson = Person("Wilson")
Wilson.greet() # "Hello, my name is Wilson"
# Python automatically passes Wilson as self
Attributes & Methods
Instance Attributes
class Person:
def __init__(self, name, age):
self.name = name # Instance attribute
self.age = age # Instance attribute
Wilson = Person("Wilson", 30)
bob = Person("Bob", 25)
Wilson.name # "Wilson"
bob.name # "Bob" - different value
Class Attributes
class Employee:
# Class attribute - shared by all instances
company = "TechCorp"
num_employees = 0
def __init__(self, name):
self.name = name
Employee.num_employees += 1 # Access class attribute
emp1 = Employee("Wilson")
emp2 = Employee("Bob")
print(Employee.num_employees) # 2
print(emp1.company) # "TechCorp"
print(emp2.company) # "TechCorp" - same value
Instance Methods
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
# Instance method - operates on instance
def deposit(self, amount):
self.balance += amount
return self.balance
def withdraw(self, amount):
if amount > self.balance:
return "Insufficient funds"
self.balance -= amount
return self.balance
def get_balance(self):
return f"{self.owner}'s balance: ${self.balance}"
account = BankAccount("Wilson", 100)
account.deposit(50) # 150
account.withdraw(30) # 120
account.get_balance() # "Wilson's balance: $120"
Class Methods
class Person:
population = 0
def __init__(self, name):
self.name = name
Person.population += 1
# Class method - operates on class, not instance
@classmethod
def get_population(cls):
return f"Total population: {cls.population}"
# Alternative constructor using class method
@classmethod
def from_birth_year(cls, name, birth_year):
age = 2024 - birth_year
return cls(name, age)
Wilson = Person("Wilson")
bob = Person("Bob")
Person.get_population() # "Total population: 2"
Static Methods
class MathOperations:
# Static method - doesn't access instance or class
@staticmethod
def add(x, y):
return x + y
@staticmethod
def multiply(x, y):
return x * y
# Call without creating instance
result = MathOperations.add(5, 3) # 8
result = MathOperations.multiply(4, 2) # 8
Constructors & Initialization
The __init__ Method
class User:
def __init__(self, username, email):
"""Constructor - called when creating new instance"""
self.username = username
self.email = email
self.active = True # Default value
print(f"User {username} created")
user = User("Wilson", "Wilson@email.com")
# Output: User Wilson created
Default Values
class Product:
def __init__(self, name, price, quantity=0):
self.name = name
self.price = price
self.quantity = quantity
laptop = Product("Laptop", 999.99) # quantity defaults to 0
mouse = Product("Mouse", 29.99, 100) # quantity is 100
Validation in Constructor
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
if balance < 0:
raise ValueError("Balance cannot be negative")
self.balance = balance
# Valid
account = BankAccount("Wilson", 100)
# Raises ValueError
# account = BankAccount("Bob", -50)
Inheritance
Basic Inheritance
# Parent class (Base class)
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Some sound"
# Child class (Derived class)
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
dog.name # "Buddy" (inherited from Animal)
dog.speak() # "Woof!" (overridden in Dog)
cat.speak() # "Meow!" (overridden in Cat)
Extending Parent Constructor
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
class Dog(Animal):
def __init__(self, name, breed):
# Call parent constructor
super().__init__(name, "Canine")
# Add child-specific attributes
self.breed = breed
def info(self):
return f"{self.name} is a {self.breed} ({self.species})"
dog = Dog("Buddy", "Golden Retriever")
dog.info() # "Buddy is a Golden Retriever (Canine)"
Multiple Inheritance
class Flyer:
def fly(self):
return "Flying..."
class Swimmer:
def swim(self):
return "Swimming..."
# Inherit from multiple classes
class Duck(Flyer, Swimmer):
def quack(self):
return "Quack!"
duck = Duck()
duck.fly() # "Flying..." (from Flyer)
duck.swim() # "Swimming..." (from Swimmer)
duck.quack() # "Quack!" (from Duck)
Method Resolution Order (MRO)
class A:
def method(self):
return "A"
class B(A):
def method(self):
return "B"
class C(A):
def method(self):
return "C"
class D(B, C):
pass
d = D()
d.method() # "B" (follows MRO: D -> B -> C -> A)
# View MRO
D.__mro__ # Shows the order: D, B, C, A, object
Polymorphism
Method Overriding
class Shape:
def area(self):
return 0
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
# Polymorphism - same method name, different behavior
shapes = [Rectangle(5, 4), Circle(3), Rectangle(2, 6)]
for shape in shapes:
print(shape.area()) # Calls appropriate area() method
Duck Typing
# "If it walks like a duck and quacks like a duck, it's a duck"
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class Duck:
def speak(self):
return "Quack!"
# Function doesn't care about type, only that object has speak()
def animal_sound(animal):
return animal.speak()
dog = Dog()
cat = Cat()
duck = Duck()
animal_sound(dog) # "Woof!"
animal_sound(cat) # "Meow!"
animal_sound(duck) # "Quack!"
Operator Overloading
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# Overload + operator
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
# Overload == operator
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __str__(self):
return f"Point({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2 # Uses __add__
print(p3) # Point(4, 6) - Uses __str__
p1 == Point(1, 2) # True - Uses __eq__
Real-World Example: RPG Game with Character Classes
This complete game example demonstrates inheritance, polymorphism, and method overriding in action.
class Character:
"""Base character class"""
def __init__(self, name, health, attack_power=10):
self.name = name
self.health = health
self.attack_power = attack_power
self.max_health = health
def attack(self, opponent):
"""Basic attack method - will be overridden by child classes"""
opponent.health -= self.attack_power
print(f"{self.name} has attacked {opponent.name} with a stick for {self.attack_power} points of damage!")
if opponent.health <= 0:
print(f"{opponent.name} has perished!")
else:
print(f"{opponent.name} has {opponent.health} health points left")
def display_stats(self):
"""Display character statistics"""
print(f"{self.name}'s stats")
print(f" Health: {self.health}/{self.max_health}")
print(f" Attack Power: {self.attack_power}")
# Child class - demonstrates INHERITANCE
class Warrior(Character):
"""Warrior character with high health and attack"""
def __init__(self, name):
# Call parent __init__ with warrior-specific values
super().__init__(name, health=150, attack_power=20)
# POLYMORPHISM - override the attack method
def attack(self, opponent):
"""Warrior's attack with unique message"""
opponent.health -= self.attack_power
print(f"{self.name} slashes at {opponent.name} with his sword, inflicting {self.attack_power} points of damage!")
if opponent.health <= 0:
print(f"{opponent.name} has perished!")
else:
print(f"{opponent.name} has {opponent.health} health points left")
def special_attack(self, opponent):
"""Warrior-specific special attack - 2x damage"""
damage = self.attack_power * 2
opponent.health -= damage
print(f"{self.name} is beating up {opponent.name} with his special attack, inflicting {damage} points of damage!")
if opponent.health <= 0:
print(f"{opponent.name} has perished!")
else:
print(f"{opponent.name} has {opponent.health} health points left")
class Mage(Character):
"""Mage character with lower health but high attack and self-heal"""
def __init__(self, name):
super().__init__(name, health=90, attack_power=75)
def attack(self, opponent):
"""Mage's attack heals self while damaging opponent"""
opponent.health -= self.attack_power
# Self-healing mechanic
heal_amount = 5
if self.health + heal_amount < self.max_health:
self.health += heal_amount
else:
self.health = self.max_health
print(f"{self.name} casts meteor strike, smashing into {opponent.name} inflicting {self.attack_power} points of damage! Also self healed by {heal_amount} points")
if opponent.health <= 0:
print(f"{opponent.name} has perished!")
else:
print(f"{opponent.name} has {opponent.health} health points left")
class EvilWizard(Character):
"""Boss character with regeneration ability"""
def __init__(self):
super().__init__(name="Evil Wizard", health=150, attack_power=40)
def regenerate(self):
"""Boss-specific ability to regenerate health"""
heal = 5
self.health += heal
print(f"{self.name} regenerates {heal} health points! Current health: {self.health}")
def create_character():
"""Character creation menu"""
name = input("What's your name adventurer? ")
print(f'''
Choose your Character
======================
1.) Warrior
2.) Mage
''')
choice = int(input("Choose: "))
if choice == 1:
return Warrior(name)
elif choice == 2:
return Mage(name)
else:
print("Invalid choice, defaulting to Warrior")
return Warrior(name)
def battle(player, boss):
"""Turn-based battle system"""
print(f"\n{'='*50}")
print(f"BATTLE: {player.name} vs {boss.name}")
print(f"{'='*50}\n")
while boss.health > 0 and player.health > 0:
print(f"""
Your Turn
==============
1.) Attack
2.) View Stats
""")
choice = input("Choose action: ")
if choice == '1':
player.attack(boss)
elif choice == '2':
player.display_stats()
boss.display_stats()
continue # Don't let boss attack if just viewing stats
else:
print("Invalid choice!")
continue
# Boss turn (if still alive)
if boss.health > 0:
boss.regenerate()
boss.attack(player)
print(f"\n{'-'*50}\n")
# Battle end
if player.health > 0:
print(f"\n{'='*50}")
print(f"{player.name} WINS!")
print(f"{'='*50}\n")
else:
print(f"\n{'='*50}")
print(f"{boss.name} wins! Game Over.")
print(f"{'='*50}\n")
def main():
"""Main game loop"""
print("Welcome to the RPG Battle Game!")
print("=" * 50)
player = create_character()
boss = EvilWizard()
print(f"\nYour character: {player.__class__.__name__}")
player.display_stats()
input("\nPress Enter to start the battle...")
battle(player, boss)
if __name__ == "__main__":
main()
Running the Game:
$ python rpg_game.py
Welcome to the RPG Battle Game!
==================================================
What's your name adventurer? Arthur
Choose your Character
======================
1.) Warrior
2.) Mage
Choose: 1
Your character: Warrior
Arthur's stats
Health: 150/150
Attack Power: 20
Press Enter to start the battle...
==================================================
BATTLE: Arthur vs Evil Wizard
==================================================
Your Turn
==============
1.) Attack
2.) View Stats
Choose action: 1
Arthur slashes at Evil Wizard with his sword, inflicting 20 points of damage!
Evil Wizard has 130 health points left
Evil Wizard regenerates 5 health points! Current health: 135
Evil Wizard has attacked Arthur with a stick for 40 points of damage!
Arthur has 110 health points left
What This Example Teaches:
| Concept | How It’s Demonstrated |
|---|---|
| Inheritance | Warrior, Mage, and EvilWizard all inherit from Character |
| Polymorphism | Each class overrides attack() with unique behavior |
| super() | Child classes call super().__init__() to initialize parent |
| Method Overriding | attack() works differently for each character type |
| Class-Specific Methods | special_attack() for Warrior, regenerate() for EvilWizard |
| Encapsulation | Health tracking and validation within methods |
| OOP Game Design | Turn-based combat, character stats, game loop |
Why This Example is Valuable:
- Complete, playable game: Students can run it and see OOP in action
- Clear inheritance hierarchy: Easy to see how child classes extend parent
- Real polymorphism: Same method (
attack) does different things - Relatable: Everyone understands RPG character classes
- Extensible: Easy to add new character types (Archer, Rogue, etc.)
- Demonstrates super(): Shows proper parent class initialization
- Game mechanics: Health management, damage calculation, turn order
- Interactive: Menu-driven, user input, game loop
Abstraction
Abstract Base Classes
from abc import ABC, abstractmethod
class Shape(ABC):
"""Abstract base class"""
@abstractmethod
def area(self):
"""Must be implemented by child classes"""
pass
@abstractmethod
def perimeter(self):
"""Must be implemented by child classes"""
pass
# Cannot instantiate abstract class
# shape = Shape() # TypeError!
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
# Must implement all abstract methods
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
rect = Rectangle(5, 4)
rect.area() # 20
rect.perimeter() # 18
Interface Pattern
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
"""Interface for payment processing"""
@abstractmethod
def process_payment(self, amount):
pass
@abstractmethod
def refund(self, amount):
pass
class CreditCardProcessor(PaymentProcessor):
def process_payment(self, amount):
return f"Processing ${amount} via credit card"
def refund(self, amount):
return f"Refunding ${amount} to credit card"
class PayPalProcessor(PaymentProcessor):
def process_payment(self, amount):
return f"Processing ${amount} via PayPal"
def refund(self, amount):
return f"Refunding ${amount} to PayPal"
# Both implement same interface
def checkout(processor, amount):
return processor.process_payment(amount)
cc = CreditCardProcessor()
pp = PayPalProcessor()
checkout(cc, 100) # "Processing $100 via credit card"
checkout(pp, 100) # "Processing $100 via PayPal"
Encapsulation
Public, Protected, and Private
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # Public
self._account_id = "12345" # Protected (convention)
self.__balance = balance # Private (name mangling)
def deposit(self, amount):
self.__balance += amount
def get_balance(self):
return self.__balance
account = BankAccount("Wilson", 1000)
# Public - accessible
account.owner # "Wilson"
# Protected - accessible but shouldn't use
account._account_id # "12345" (but shouldn't access directly)
# Private - not directly accessible
# account.__balance # AttributeError!
account.get_balance() # 1000 (use public method)
Property Decorators
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
# Getter
@property
def age(self):
return self._age
# Setter
@age.setter
def age(self, value):
if value < 0:
raise ValueError("Age cannot be negative")
self._age = value
# Read-only property (no setter)
@property
def name(self):
return self._name
person = Person("Wilson", 30)
# Use like regular attribute, but with validation
person.age = 31 # Uses setter
print(person.age) # Uses getter: 31
# person.age = -5 # Raises ValueError
# person.name = "Bob" # AttributeError (no setter)
Getters and Setters (Classic Style)
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
def get_celsius(self):
return self._celsius
def set_celsius(self, value):
if value < -273.15:
raise ValueError("Temperature below absolute zero!")
self._celsius = value
def get_fahrenheit(self):
return self._celsius * 9/5 + 32
temp = Temperature(25)
temp.get_celsius() # 25
temp.get_fahrenheit() # 77.0
temp.set_celsius(30)
Special Methods (Magic Methods)
String Representation
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
# For print() and str()
def __str__(self):
return f"{self.title} by {self.author}"
# For developers/debugging
def __repr__(self):
return f"Book('{self.title}', '{self.author}')"
book = Book("1984", "George Orwell")
print(book) # "1984 by George Orwell" (uses __str__)
str(book) # "1984 by George Orwell"
repr(book) # "Book('1984', 'George Orwell')"
Comparison Operators
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
"""== operator"""
return self.age == other.age
def __lt__(self, other):
"""< operator"""
return self.age < other.age
def __le__(self, other):
"""<= operator"""
return self.age <= other.age
def __gt__(self, other):
"""> operator"""
return self.age > other.age
Wilson = Person("Wilson", 30)
bob = Person("Bob", 25)
Wilson == bob # False
Wilson > bob # True
Wilson < bob # False
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 __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(1, 1)
v3 = v1 + v2 # Vector(3, 4)
v4 = v1 - v2 # Vector(1, 2)
v5 = v1 * 2 # Vector(4, 6)
Container Methods
class Playlist:
def __init__(self):
self.songs = []
def add_song(self, song):
self.songs.append(song)
def __len__(self):
"""len() function"""
return len(self.songs)
def __getitem__(self, index):
"""[] operator"""
return self.songs[index]
def __contains__(self, song):
"""'in' operator"""
return song in self.songs
playlist = Playlist()
playlist.add_song("Song A")
playlist.add_song("Song B")
playlist.add_song("Song C")
len(playlist) # 3
playlist[0] # "Song A"
"Song B" in playlist # True
"Song D" in playlist # False
# Can iterate because of __getitem__
for song in playlist:
print(song)
Context Manager
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
"""Called when entering 'with' block"""
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
"""Called when exiting 'with' block"""
if self.file:
self.file.close()
# Use with 'with' statement
with FileManager("test.txt", "w") as f:
f.write("Hello, World!")
# File automatically closed
OOP Design Principles
SOLID Principles Overview
# S - Single Responsibility Principle
# A class should have one reason to change
# Bad
class User:
def save_to_database(self):
pass
def send_email(self):
pass
# Good - separate responsibilities
class User:
pass
class UserRepository:
def save(self, user):
pass
class EmailService:
def send(self, to, message):
pass
# O - Open/Closed Principle
# Open for extension, closed for modification
class Discount:
def calculate(self, price):
return price
class StudentDiscount(Discount):
def calculate(self, price):
return price * 0.9 # 10% off
class SeniorDiscount(Discount):
def calculate(self, price):
return price * 0.85 # 15% off
# L - Liskov Substitution Principle
# Subtypes must be substitutable for base types
class Bird:
def move(self):
return "Flying"
class Penguin(Bird):
def move(self):
return "Swimming" # Still valid movement
# I - Interface Segregation Principle
# Many specific interfaces better than one general
from abc import ABC, abstractmethod
class Printer(ABC):
@abstractmethod
def print(self):
pass
class Scanner(ABC):
@abstractmethod
def scan(self):
pass
class BasicPrinter(Printer):
def print(self):
pass
class MultifunctionDevice(Printer, Scanner):
def print(self):
pass
def scan(self):
pass
# D - Dependency Inversion Principle
# Depend on abstractions, not concretions
class Database(ABC):
@abstractmethod
def save(self, data):
pass
class MySQLDatabase(Database):
def save(self, data):
print("Saving to MySQL")
class UserService:
def __init__(self, database: Database):
self.db = database # Depends on abstraction
def create_user(self, user_data):
self.db.save(user_data)
See Also
- Python Basics Cheat Sheet - Python fundamentals
- Decorators Cheat Sheet - @property, @staticmethod, @classmethod decorators
- Error Handling Cheat Sheet - Custom exceptions and error handling
- Testing and Debugging Cheat Sheet - Testing OOP code