Python OOP Cheat Sheet
Quick-reference for Python classes and object-oriented patterns. Each section includes copy-ready snippets with inline output comments.
Class Definition and __init__
__init__ sets up instance attributes when an object is created. self is the instance.
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def bark(self):
return f'{self.name} says woof!'
fido = Dog('Fido', 'Lab')
fido.bark() # => 'Fido says woof!'class Counter:
def __init__(self, start=0):
self.count = start
def increment(self):
self.count += 1
c = Counter()
c.increment()
c.count # => 1Instance vs Class Attributes
Class attributes are shared across all instances. Instance attributes (self.x) are per-instance.
class Dog:
species = 'Canis familiaris' # class attribute
Dog.species # => 'Canis familiaris'
Dog().species # => 'Canis familiaris' (same object)class Bad:
items = [] # shared across ALL instances!
a, b = Bad(), Bad()
a.items.append(1)
b.items # => [1] (oops, shared!)class Good:
def __init__(self):
self.items = [] # each instance gets its own
a, b = Good(), Good()
a.items.append(1)
b.items # => [] (independent)Rule: use class attributes for shared constants. Use self.x in __init__ for per-instance state.
Inheritance and super()
Subclasses inherit methods and attributes. Call super().__init__() to run the parent's initialization.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f'{self.name} makes a sound'
class Dog(Animal):
def speak(self):
return f'{self.name} barks'
Dog('Rex').speak() # => 'Rex barks'class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # parent sets self.name
self.breed = breed
d = Dog('Rex', 'Lab')
d.name # => 'Rex'
d.breed # => 'Lab'class Dog(Animal):
def __init__(self, name, breed):
self.breed = breed
# forgot super().__init__(name)!
d = Dog('Rex', 'Lab')
d.name # AttributeError: 'Dog' has no attribute 'name'isinstance(Dog('Rex', 'Lab'), Animal) # => True
issubclass(Dog, Animal) # => TrueDunder Methods (__str__, __repr__, __eq__, __len__)
Special methods (double underscore) let your objects work with Python syntax and built-in functions.
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f'Point({self.x!r}, {self.y!r})'
repr(Point(3, 4)) # => 'Point(3, 4)'class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return f'({self.x}, {self.y})'
def __repr__(self):
return f'Point({self.x!r}, {self.y!r})'
print(Point(3, 4)) # => '(3, 4)' (uses __str__)If only one is defined, implement __repr__. Python uses it as fallback for str().
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y
Point(1, 2) == Point(1, 2) # => Trueclass Playlist:
def __init__(self, songs):
self.songs = songs
def __len__(self):
return len(self.songs)
def __bool__(self):
return len(self.songs) > 0
p = Playlist(['a', 'b'])
len(p) # => 2
bool(p) # => Trueclass Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
n = self.start
while n > 0:
yield n
n -= 1
list(Countdown(3)) # => [3, 2, 1]@property (Computed / Validated Attributes)
Use @property to add validation or computation behind attribute access syntax.
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14159 * self.radius ** 2
c = Circle(5)
c.area # => 78.53975 (no parentheses)class Person:
def __init__(self, age):
self.age = age # uses the setter
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError('Age cannot be negative')
self._age = value
p = Person(25)
# p.age = -1 # ValueError: Age cannot be negativeclass User:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.deleter
def name(self):
self._name = 'Anonymous'
u = User('Alice')
del u.name
u.name # => 'Anonymous'@dataclass (Auto-Generated Classes)
Automatically generates __init__, __repr__, and __eq__. Ideal for data containers.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p = Point(1.0, 2.0)
repr(p) # => 'Point(x=1.0, y=2.0)'
p == Point(1.0, 2.0) # => Truefrom dataclasses import dataclass, field
@dataclass
class Config:
host: str = 'localhost'
port: int = 8080
tags: list[str] = field(default_factory=list)Use field(default_factory=list) for mutable defaults. Never use tags: list = [].
@dataclass(frozen=True)
class Color:
r: int
g: int
b: int
c = Color(255, 0, 0)
# c.r = 128 # FrozenInstanceError@dataclass(slots=True)
class Vector:
x: float
y: float
z: float
# Uses __slots__ instead of __dict__ for lower memoryAbstract Classes (abc)
Abstract base classes define interfaces. Subclasses must implement all abstract methods.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
...
@abstractmethod
def perimeter(self):
...
# Shape() # TypeError: Can't instantiate abstract classclass Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
c = Circle(5)
c.area() # => 78.53975
c.perimeter() # => 31.4159class Shape(ABC):
@property
@abstractmethod
def name(self) -> str:
...@classmethod vs @staticmethod
@classmethod receives cls (the class). @staticmethod receives nothing (just a namespaced function).
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def from_string(cls, s):
y, m, d = map(int, s.split('-'))
return cls(y, m, d)
d = Date.from_string('2026-02-22')
d.year # => 2026class MathUtils:
@staticmethod
def is_even(n):
return n % 2 == 0
MathUtils.is_even(4) # => True# @classmethod: needs access to class (cls)
# - Alternative constructors (from_json, from_dict)
# - Factory methods that return instances
#
# @staticmethod: no access to class or instance
# - Utility functions that belong to the class namespace
# - Validation helpersComposition vs Inheritance
class Animal:
def eat(self):
return 'eating'
class Dog(Animal): # Dog IS an Animal
def bark(self):
return 'woof'
Dog().eat() # => 'eating' (inherited)class Engine:
def start(self):
return 'vroom'
class Car:
def __init__(self):
self.engine = Engine() # Car HAS an Engine
def start(self):
return self.engine.start()
Car().start() # => 'vroom'# Prefer composition when:
# - You need to swap implementations
# - You want to avoid inheriting unwanted methods
# - The relationship is "uses" not "is a kind of"
# - Multiple behaviors would require multiple inheritanceFavor composition over inheritance when in doubt. It is more flexible and avoids diamond inheritance problems.
Private Attributes and Name Mangling
class User:
def __init__(self, name):
self._name = name # "private by convention"
u = User('Alice')
u._name # => 'Alice' (still accessible, but signals "internal")class User:
def __init__(self):
self.__secret = 42
u = User()
# u.__secret # AttributeError
u._User__secret # => 42 (name-mangled to _ClassName__attr)Double underscore triggers name mangling to prevent accidental override in subclasses. Single underscore is preferred for most "private" attributes.
Common TypeError Fixes
class Bad:
def greet(): # missing self!
return 'hi'
# Bad().greet()
# TypeError: greet() takes 0 positional arguments but 1 was givenclass Dog:
def bark(self):
return 'woof'
# Dog.bark() # TypeError: missing argument 'self'
Dog().bark() # => 'woof' (correct)# Bad: shared default list
class Bad:
def __init__(self, items=[]): # shared!
self.items = items
# Good: use None sentinel
class Good:
def __init__(self, items=None):
self.items = items if items is not None else []Can you write this from memory?
Write the class header for a class named `Item` (header only, no body)