Python OOP Cheat Sheet
Object-oriented Python covering classes, inheritance, dunder methods, computed properties, class/static methods, and abstract base classes.
Classes & Instances
Defining a class with instance and class attributes.
class Dog: species = "Canis familiaris" # class attribute, shared def __init__(self, name, age): self.name = name # instance attribute self.age = age def bark(self): return f"{self.name} says Woof!"d = Dog("Rex", 3)d.bark() # 'Rex says Woof!'
Inheritance & super()
Extending classes and calling parent implementations.
class Animal: def __init__(self, name): self.name = name def speak(self): raise NotImplementedErrorclass Cat(Animal): def speak(self): return f"{self.name} says Meow"class Kitten(Cat): def speak(self): return super().speak() + " (softly)"
Common Dunder Methods
Special methods that customize built-in behavior.
- __init__(self, ...)- called when an instance is created, sets up attributes
- __repr__(self)- unambiguous developer-facing string representation
- __str__(self)- readable user-facing string, used by print() and str()
- __eq__(self, other)- defines behavior for the == operator
- __len__(self)- defines behavior for len(obj)
- __add__(self, other)- defines behavior for the + operator
- __iter__(self)- makes an object iterable with for loops
- __enter__ / __exit__- implement the context manager protocol for with statements
Properties, classmethod & staticmethod
Computed attributes and alternative constructors.
class Circle: def __init__(self, radius): self._radius = radius @property def area(self): return 3.14159 * self._radius ** 2 @classmethod def unit_circle(cls): return cls(1) # receives the class, not an instance @staticmethod def is_valid_radius(r): return r > 0 # no access to self or clsc = Circle(2)c.area # computed on access, no parentheses needed
Abstract Base Classes
Enforcing that subclasses implement required methods.
from abc import ABC, abstractmethodclass Shape(ABC): @abstractmethod def area(self): ...class Square(Shape): def __init__(self, side): self.side = side def area(self): return self.side ** 2# Shape() # raises TypeError: Can't instantiate abstract class
Multiple Inheritance & MRO
Python resolves attribute lookups across multiple base classes using C3 linearization.
class A: def hello(self): return "A"class B(A): def hello(self): return "B->" + super().hello()class C(A): def hello(self): return "C->" + super().hello()class D(B, C): passD().hello() # 'B->C->A' -- follows the MRO, not naive left-to-rightD.__mro__ # (D, B, C, A, object)
__slots__ for Memory & Speed
Restricts instance attributes to a fixed set, skipping the per-instance __dict__.
class Point: __slots__ = ("x", "y") def __init__(self, x, y): self.x = x self.y = yp = Point(1, 2)p.z = 3 # AttributeError: 'Point' object has no attribute 'z'# Trade-off: no __dict__, no arbitrary attributes, but lower memory# and faster attribute access -- valuable for classes with millions of instances
Metaclasses: Customizing Class Creation
A metaclass controls how a class itself is built, not just its instances.
class UpperAttrMeta(type): def __new__(mcs, name, bases, namespace): upper_ns = { (k.upper() if not k.startswith("__") else k): v for k, v in namespace.items() } return super().__new__(mcs, name, bases, upper_ns)class Demo(metaclass=UpperAttrMeta): x = 1Demo.X # 1 -- attribute name was uppercased at class-creation time
Descriptors: How @property Really Works
Objects implementing __get__/__set__ let you reuse attribute logic across classes.
class PositiveNumber: def __set_name__(self, owner, name): self.name = "_" + name def __get__(self, instance, owner): return getattr(instance, self.name) def __set__(self, instance, value): if value <= 0: raise ValueError(f"{self.name} must be positive") setattr(instance, self.name, value)class Account: balance = PositiveNumber() def __init__(self, balance): self.balance = balance # goes through PositiveNumber.__set__
Composition, Mixins & Dataclasses
Idioms for structuring larger object-oriented codebases.
- Mixin class- a class not meant to be instantiated alone, added via multiple inheritance to bolt on reusable behavior (e.g. LoggingMixin)
- "favor composition over inheritance"- hold a collaborator object as an attribute instead of inheriting from it, avoiding deep/fragile hierarchies
- @dataclass- auto-generates __init__, __repr__ and __eq__ from type-annotated class attributes
- @dataclass(frozen=True)- makes instances immutable and hashable, raising on attribute assignment after __init__
- typing.Protocol- defines structural (duck-typed) interfaces checked by static type checkers without requiring inheritance
- __init_subclass__(cls, **kwargs)- hook called automatically whenever a subclass is defined, useful for plugin registration
- copy.deepcopy(obj)- recursively duplicates an object graph, honoring a custom __deepcopy__ if defined
Implement `__repr__` on every class you write, even quick ones — it makes debugging and REPL sessions vastly easier, and if `__str__` is missing Python falls back to `__repr__` automatically.