Mastering the Context Object Pattern in Python
Overview
Passing five or six arguments through every function layer creates a maintenance nightmare. This "parameter bloat" often signals that your functions lack a cohesive way to handle shared environmental data. The context object design pattern pattern solves this by grouping related dependencies—such as database sessions, loggers, and user IDs—into a single object. This technique simplifies function signatures and makes your API much cleaner without resorting to globally accessible singletons.
Prerequisites
To follow this guide, you should understand:
- Python Type Hinting: Familiarity with
typingand class-based annotations. - Data Classes: Knowledge of the
@dataclassdecorator for creating concise data containers. - Basic SQL Alchemy: Understanding how database sessions and queries function.

Key Libraries & Tools
- Dataclasses: A standard Python library for generating boilerplate code in classes.
- Protocols: Part of the
typingmodule used for structural subtyping (duck typing). - SQLAlchemy: An ORM used here to manage database sessions.
- Logging: Python's built-in module for tracking runtime events.
Code Walkthrough
Defining the Context Object
First, we group our common dependencies into a single Data Class. This object acts as the "environment" for our operations.
from dataclasses import dataclass
from typing import Any
import logging
@dataclass
class AppContext:
user_id: int
db: Any
logger: logging.Logger
config: dict[str, Any]
Refactoring High-Level Functions
Instead of passing individual variables, we pass the AppContext. This drastically reduces the noise in high-level business logic.
def publish_article(article_id: int, context: AppContext):
# Accessing shared dependencies via the context
context.logger.info(f"Processing article {article_id}")
article = retrieve_article(article_id, context.db, context.logger)
if article:
html = render_article(article, context.logger)
send_to_api(html, context.config["api_key"])
Syntax Notes
- Dot Notation: Accessing
context.loggerinstead of a localloggervariable makes the source of the dependency explicit. - Protocols for Decoupling: Instead of depending on concrete types like
sqlalchemy.Session, usetyping.Protocolto define what the context needs. This allows you to swap real dependencies for mocks during testing.
Practical Examples
- Web Frameworks: Django uses a context dictionary to pass data from views to templates.
- Request Handling: Backend systems often use a
Requestobject that carries headers, user authentication, and payload data through various middleware layers.
Tips & Gotchas
- Avoid the God Object: Never throw every possible variable into your context. If a low-level utility function only needs an integer, pass the integer, not the whole context.
- High-Level vs. Low-Level: Reserve context objects for high-level orchestrators. Low-level functions should remain "pure" and focused to prevent tight coupling.
- Testing: Use the context object to inject mocks. By passing a mock context to your function, you can simulate database failures or log captures easily.
- ArjanCodes
- 14%· people
- builder pattern
- 14%· python design patterns
- context object design pattern
- 14%· python design patterns
- Django
- 14%· software
- Python
- 14%· software
- Other topics
- 29%

Too Many Function Arguments? Use This Pattern
WatchArjanCodes // 19:45
On this channel, I post videos about programming and software design to help you take your coding skills to the next level. I'm an entrepreneur and a university lecturer in computer science, with more than 20 years of experience in software development and design. If you're a software developer and you want to improve your development skills, and learn more about programming in general, make sure to subscribe for helpful videos. I post a video here every Friday. If you have any suggestion for a topic you'd like me to cover, just leave a comment on any of my videos and I'll take it under consideration. Thanks for watching!