Lesson 30 โข Advanced
Mixins, Multiple Inheritance & OOP Patterns
Learn the REAL Python OOP system used by Django, FastAPI, SQLAlchemy, Pydantic, PyTorch, and Scrapy. Master mixins, multiple inheritance, and professional OOP patterns used in large-scale frameworks.
๐ฅ Mixins, Multiple Inheritance & OOP Patterns
This lesson teaches the REAL Python OOP system used by:
| Framework | Common Mixins | Purpose |
|---|---|---|
| Django | LoginRequiredMixin, PermissionMixin | Authentication & authorization |
| SQLAlchemy | TimestampMixin, SerializerMixin | Database models & serialization |
| FastAPI | ModelConfigMixin | Configuration & validation |
| Scrapy | RetryMixin, LogMixin | Robustness & debugging |
This is how large Python frameworks organise reusable behaviour at scale.
โญ 1. What Are Mixins?
A mixin is:
- โ a small class
- โ provides one specific behaviour
- โ not meant to be instantiated on its own
- โ used to "mix" extra features into other classes
Example of a simple mixin:
Simple Mixin Example
A mixin that adds a pretty() method to any class
class PrintableMixin:
def pretty(self):
return f"<{self.__class__.__name__}: {self.__dict__}>"
class User(PrintableMixin):
def __init__(self, name):
self.name = name
# Now:
print(User("Boopie").pretty())Mixins = adding reusable abilities, not designing base types.
โญ 2. Why Mixins Exist
Large codebases avoid copying logic.
Instead, they break features into "behaviour modules":
Examples:
- โ LoggingMixin
- โ PermissionMixin
- โ RateLimitMixin
- โ CacheMixin
- โ TokenAuthMixin
- โ SerializerMixin
- โ RetryMixin
- โ EventEmitterMixin
Each mixin provides ONE capability.
Your class can combine many of them:
Combining Multiple Mixins
A class that combines multiple mixin behaviours
# Example of combining multiple mixins
class AuthMixin:
def authenticate(self):
print("Authenticating...")
class LoggingMixin:
def log(self, msg):
print(f"[LOG] {msg}")
class RetryMixin:
def retry(self, func, attempts=3):
for i in range(attempts):
try:
return func()
except Exception as e:
print(f"Attempt {i+1} failed: {e}")
class CacheMixin:
_cache = {}
def cache_get(self, key):
return
...This avoids the inheritance mess of shoving everything into one giant parent class.
โญ 3. When NOT To Use Mixins
Do NOT use mixins for:
- โ complex logic
- โ core business functionality
- โ storage of critical state
- โ large method sets
- โ tightly coupled behaviour
Use mixins for:
- โ helpers
- โ behaviour
- โ convenience methods
- โ add-on features
- โ small utilities
โ๏ธ 4. Multiple Inheritance โ How Python Makes This Work
Python supports full multiple inheritance:
Multiple Inheritance Basics
A class inheriting from multiple parent classes
class A:
def greet(self):
return "Hello from A"
class B:
def greet(self):
return "Hello from B"
class C(A, B):
pass
# C inherits from both A and B
c = C()
print(c.greet()) # prints "Hello from A" because A is firstBehind the scenes, Python uses:
๐ MRO (Method Resolution Order)
This determines which class Python checks first when calling a method.
โญ 5. How to View the MRO
Critical for debugging inheritance:
Viewing the MRO
Check the Method Resolution Order for debugging
class A:
def speak(self): print("A")
class B:
def speak(self): print("B")
class C(A, B):
pass
# View the Method Resolution Order
print(C.mro())
# Or use __mro__
print(C.__mro__)
# Output example:
# [<class C>, <class A>, <class B>, <class object>]This explains:
- โ method lookup
- โ attribute access
- โ super() behaviour
- โ complex inheritance chains
- โ how Python avoids the diamond problem
๐ท 6. The Diamond Problem (and Python's solution)
Diamond inheritance:
A
/ \
B C
\ /
DAll classes inherit from A.
Without control โ chaos.
Python solves this using C3 linearization.
This ensures:
- โ deterministic lookup order
- โ no ambiguity
- โ super() always moves to the next class in the hierarchy
โญ 7. Why super() Matters in Multiple Inheritance
Many languages treat super() like:
โก๏ธ "call ONLY the parent class"
Python treats super() as:
โก๏ธ "call the NEXT class in the MRO chain"
This allows cooperative multiple inheritance.
super() in Multiple Inheritance
See how super() chains through the MRO
class A:
def process(self):
print("A")
class B:
def process(self):
print("B")
class C:
def process(self):
print("C")
class D(A, B, C):
def process(self):
print("D")
super().process()
# Watch the chain work!
d = D()
d.process()
print("\nMRO:", [cls.__name__ for cls in D.mro()])That's the power of super() + MRO.
โก 8. Designing Cooperative Mixins
Every mixin you write should:
- โ call super()
- โ accept *args, **kwargs
- โ avoid breaking the chain
Example:
Cooperative Mixins
Mixins that properly chain with super()
class LoggingMixin:
def save(self, *args, **kwargs):
print("Logging save...")
return super().save(*args, **kwargs)
class ValidationMixin:
def save(self, *args, **kwargs):
print("Validating...")
return super().save(*args, **kwargs)
class BaseModel:
def save(self, *args, **kwargs):
print("Saving to database!")
return True
class User(LoggingMixin, ValidationMixin, BaseModel):
pass
user = User()
user.save()If every class cooperates, Python can thread all behaviours together.
This is how Django CBVs (Class-Based Views) work internally.
๐ถ 9. Mixins Should NOT Have __init__
Mixins generally avoid __init__ because it breaks cooperative inheritance.
If needed, you MUST write:
Mixin with __init__
Proper way to use __init__ in mixins
class ConfigMixin:
def __init__(self, *args, **kwargs):
self.debug = True
super().__init__(*args, **kwargs)
class BaseClass:
def __init__(self, name):
self.name = name
class MyClass(ConfigMixin, BaseClass):
pass
obj = MyClass("Test")
print(f"Name: {obj.name}, Debug: {obj.debug}")Otherwise, your mixin may block other parents from initialising.
โญ 10. Real-World Mixin Examples
Examples from popular frameworks:
Real-World Framework Mixins
Examples from Django, SQLAlchemy, and FastAPI
# Django-style mixins
class LoginRequiredMixin:
"""Requires user to be logged in"""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return "Redirect to login"
return super().dispatch(request, *args, **kwargs)
class PermissionRequiredMixin:
"""Requires specific permissions"""
permission_required = None
def has_permission(self):
return self.permission_required is not None
# SQLAlchemy-style mixin
cla
...Mixins dominate real frameworks.
๐ง 11. Interface Mixins (Behaviour Contracts)
You can define behaviour expectations:
Interface Mixins
Defining behaviour contracts with mixins
class JSONSerializableMixin:
def to_json(self):
raise NotImplementedError("Subclass must implement to_json()")
class XMLSerializableMixin:
def to_xml(self):
raise NotImplementedError("Subclass must implement to_xml()")
class User(JSONSerializableMixin):
def __init__(self, name, age):
self.name = name
self.age = age
def to_json(self):
return f'{{"name": "{self.name}", "age": {self.age}}}'
user = User("Boopie", 16)
print(user.to_json(
...Used when:
- โ you need consistency
- โ library expects a method to exist
- โ plugin systems
๐ฅ 12. Multiple Inheritance for Role Composition
Rather than long inheritance like:
AdminUser โ User โ BaseUser โ Object
You use role stacking:
Role Composition
Composing roles using multiple inheritance
class AuthMixin:
def authenticate(self):
return True
class PermissionMixin:
permissions = []
def has_permission(self, perm):
return perm in self.permissions
class LoggingMixin:
def log_action(self, action):
print(f"[LOG] {action}")
class BaseUser:
def __init__(self, name):
self.name = name
class AdminUser(AuthMixin, PermissionMixin, LoggingMixin, BaseUser):
permissions = ["read", "write", "delete", "admin"]
admin = AdminUser("SuperAdmi
...Roles = flexibility.
This is scalable for 10+ behaviour components.
๐งฉ 13. Composition vs Inheritance (Critical Design Decision)
โ Use inheritance when:
- โข "is a" relationship
- โข class hierarchy makes sense
- โข polymorphic behaviour
โ Use composition when:
- โข "has a" relationship
- โข behaviour shouldn't leak
- โข state belongs to specific objects
Example:
โ BAD: Dog inherits from Engine
โ GOOD: Car has an Engine object
๐งฑ 14. Pattern: Behaviour Trees With Mixins
Create classes with distinct abilities:
Behaviour Trees With Mixins
Composing game character abilities
class MoveMixin:
def move(self, direction):
print(f"Moving {direction}")
class AttackMixin:
def attack(self, target):
print(f"Attacking {target}!")
class HealMixin:
def heal(self, amount):
print(f"Healing for {amount} HP")
# Compose characters:
class Warrior(MoveMixin, AttackMixin):
pass
class Mage(MoveMixin, HealMixin, AttackMixin):
pass
class Healer(MoveMixin, HealMixin):
pass
# Test the characters
warrior = Warrior()
warrior.move("forward"
...This is used in:
- โ games
- โ AI agents
- โ simulations
- โ robotics
๐งต 15. Pattern: Mechanical Mixins (Deep Automation)
Advanced mixins generate code:
Auto-generating Methods
Mixins that automatically generate __repr__ and __eq__
class AutoReprMixin:
def __repr__(self):
attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
return f"{self.__class__.__name__}({attrs})"
class AutoEqMixin:
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return self.__dict__ == other.__dict__
# Plug them in:
class User(AutoReprMixin, AutoEqMixin):
def __init__(self, name, age):
self.name = name
self.age = age
u1 = User("Boopi
...Framework-level behaviour.
๐ Quick Reference โ Mixins & Multiple Inheritance
| Concept | What it does |
|---|---|
| class C(A, B): | Multiple inheritance โ inherits from A and B |
| C.__mro__ | Method Resolution Order for lookups |
| super() | Call next class in MRO chain |
| class LogMixin: | Reusable behaviour added via inheritance |
| isinstance(obj, Mixin) | Check if mixin is applied |
๐ Great work! You've completed this lesson.
You can now compose reusable behaviours with mixins and reason about MRO โ the same pattern Django's class-based views use.
Up next: Design Patterns โ apply proven solutions to common software architecture problems.
Sign up for free to track which lessons you've completed and get learning reminders.