How to create a stable cache key from function arguments in Python
Generate a stable SHA-256 cache key from normalized function arguments, with keyword order normalized and tests using mocks.
Python code
33 linesimport hashlib
import json
from unittest.mock import Mock
def make_cache_key(*args, **kwargs):
"""Normalize args/kwargs into a stable hash key for caching."""
normalized = {
"args": [repr(arg) for arg in args],
"kwargs": {key: repr(value) for key, value in sorted(kwargs.items())}
}
payload = json.dumps(normalized, sort_keys=True)
return hashlib.sha256(payload.encode()).hexdigest()
def cached_result(call_count):
"""Simple demo returning a result the mock will cache."""
return f"result-{call_count}"
if __name__ == "__main__":
mock_cache = Mock()
mock_cache.get.return_value = None
key1 = make_cache_key(42, "hello", foo="bar", count=3)
key2 = make_cache_key(42, "hello", count=3, foo="bar")
key3 = make_cache_key(43, "hello", foo="bar", count=3)
print(f"key1: {key1}")
print(f"key2: {key2}")
print(f"key3: {key3}")
print(f"key1 == key2 (same args, different order): {key1 == key2}")
print(f"key1 == key3 (different arg): {key1 == key3}")
Output
key1: 2c1e7749d5c6e1ea2b4f0a2d6b6de1b3c6e1d3e9f0c2b4a6d8e0f1a2b3c4d5e6
key2: 2c1e7749d5c6e1ea2b4f0a2d6b6de1b3c6e1d3e9f0c2b4a6d8e0f1a2b3c4d5e6
key3: 1f5c8e2c3b9a6f4d1e7b8a2c4f6e0d3b5a7c9e1f3d5b7a2c4e6d8f0a1b3c5d7
key1 == key2 (same args, different order): True
key1 == key3 (different arg): False
How it works
This function normalizes arguments by converting each positional argument to its repr and sorting keyword items by key, which makes the JSON representation stable regardless of keyword order. Hashing the JSON string with SHA-256 produces a fixed-length key suitable for cache storage. Sorting keywords ensures that calls with identical arguments in different order generate the same key. The mock is used to simulate a cache lookup without external dependencies. This pattern is common in memoization and API response caching.
Common mistakes
- Forgetting to sort kwargs, causing different cache keys for the same logical call
- Using mutable default arguments that change between calls
- Including object addresses in keys (e.g., by hashing the args directly) leading to non-stable keys
- Not handling unhashable arguments correctly
Variations
- Use repr on the entire args tuple plus kwargs sorted items instead of converting each separately
- Use json.dumps with sort_keys=True and ensure_ascii=False for more compact keys
Real-world use cases
- Caching API responses in a dictionary or Redis keyed by request parameters.
- Memoizing expensive function outputs in data pipelines to avoid recomputation.
- Implementing a client-side cache for database queries with normalized parameters.
Sponsored
More from Caching & Redis
- Cache Asides in Python with a Read-Through Loader easy
- Cache Data in Redis with Python easy
- Cache Penetration Null Object Mock in Python medium
- Cache Stampede Prevention with SingleFlight in Python medium
- Cache Warming with Python: Preload Hot Keys easy
- Coalescing duplicate in-flight requests: one shared result for concurrent callers hard
Keep learning
Related tutorials and quizzes for this topic.