Skip to content

Instantly share code, notes, and snippets.

@nrbnlulu
Created August 6, 2025 09:39
Show Gist options
  • Save nrbnlulu/59fb9fb881887c4c7795cb54938c60f2 to your computer and use it in GitHub Desktop.
Save nrbnlulu/59fb9fb881887c4c7795cb54938c60f2 to your computer and use it in GitHub Desktop.
cached python method
def memoized_method[**Ps, T](hash_fn: Callable[Ps, int] | None = None) -> Callable[[Callable[Ps, T]], Callable[Ps, T]]:
def wrapper(fn: Callable[Ps, T]) -> Callable[Ps, T]:
@functools.wraps(fn)
def ret(self, *args, **kwargs) -> T:
try:
cache: LRUCache[int, T] = self.__memoized_cache__
except AttributeError:
self.__memoized_cache__ = cache = LRUCache(capacity=100)
hash_key = hash_fn(self, *args, **kwargs) if hash_fn else hash((args, frozendict(kwargs.items())))
if found := cache.get(hash_key):
return found
result = fn(self, *args, **kwargs)
cache[hash_key] = result
return result
return ret
return wrapper
import gc
import weakref
from unittest.mock import Mock
from core.utils import memoized_method
def test_memoized_method_caches_results() -> None:
"""Test that memoized_method caches results for repeated calls."""
mock_obj = Mock()
call_count = 0
@memoized_method()
def expensive_method(self, x: int, y: int) -> int:
nonlocal call_count
call_count += 1
return x + y
# First call should execute the method
result1 = expensive_method(mock_obj, 1, 2)
assert result1 == 3
assert call_count == 1
# Second call with same args should return cached result
result2 = expensive_method(mock_obj, 1, 2)
assert result2 == 3
assert call_count == 1 # Should not increment
# Call with different args should execute the method again
result3 = expensive_method(mock_obj, 2, 3)
assert result3 == 5
assert call_count == 2
def test_memoized_method_with_kwargs() -> None:
"""Test that memoized_method works with keyword arguments."""
mock_obj = Mock()
call_count = 0
@memoized_method()
def method_with_kwargs(self, x: int, y: int = 10, z: str = "default") -> str:
nonlocal call_count
call_count += 1
return f"{x}-{y}-{z}"
# First call
result1 = method_with_kwargs(mock_obj, 1, y=20, z="test")
assert result1 == "1-20-test"
assert call_count == 1
# Same call should be cached
result2 = method_with_kwargs(mock_obj, 1, y=20, z="test")
assert result2 == "1-20-test"
assert call_count == 1
# Different kwargs should not be cached
result3 = method_with_kwargs(mock_obj, 1, y=30, z="test")
assert result3 == "1-30-test"
assert call_count == 2
def test_memoized_method_with_custom_hash_function() -> None:
"""Test that memoized_method works with a custom hash function."""
mock_obj = Mock()
call_count = 0
def custom_hash(self, x: int, y: int) -> int:
# Custom hash that only considers x, ignoring y
return hash(x)
@memoized_method(hash_fn=custom_hash)
def method_with_custom_hash(self, x: int, y: int) -> int:
nonlocal call_count
call_count += 1
return x * y
# First call
result1 = method_with_custom_hash(mock_obj, 2, 3)
assert result1 == 6
assert call_count == 1
# Call with same x but different y should return cached result
# because custom hash only considers x
result2 = method_with_custom_hash(mock_obj, 2, 5)
assert result2 == 6 # Returns cached result, not 2*5=10
assert call_count == 1
# Call with different x should execute method
result3 = method_with_custom_hash(mock_obj, 3, 4)
assert result3 == 12
assert call_count == 2
def test_memoized_method_preserves_function_metadata() -> None:
"""Test that memoized_method preserves the original function's metadata."""
mock_obj = Mock()
@memoized_method()
def documented_method(self, x: int) -> int:
"""This is a documented method."""
return x * 2
assert documented_method.__name__ == "documented_method"
assert documented_method.__doc__ == "This is a documented method."
def test_memoized_method_separate_instance_caches() -> None:
"""Test that different instances have separate caches."""
mock_obj1 = Mock()
mock_obj2 = Mock()
call_count = 0
@memoized_method()
def instance_method(self, x: int) -> int:
nonlocal call_count
call_count += 1
return x * 10
# Call with first object
result1 = instance_method(mock_obj1, 5)
assert result1 == 50
assert call_count == 1
# Call with second object but same args - should execute again
# because each instance has its own cache
result2 = instance_method(mock_obj2, 5)
assert result2 == 50
assert call_count == 2 # Should increment for different instance
# Call with first object again - should use cached result
result3 = instance_method(mock_obj1, 5)
assert result3 == 50
assert call_count == 2 # Should not increment
# Call with second object again - should use cached result
result4 = instance_method(mock_obj2, 5)
assert result4 == 50
assert call_count == 2 # Should not increment
def test_memoized_method_garbage_collection() -> None:
"""Test that the cache doesn't prevent garbage collection of instances."""
call_count = 0
class TestClass:
def __init__(self, value: int):
self.value = value
@memoized_method()
def cached_method(self, x: int) -> int:
nonlocal call_count
call_count += 1
return self.value + x
# Create an instance and call the cached method
obj = TestClass(10)
weak_ref = weakref.ref(obj)
# Call the method to create cache
result = obj.cached_method(5)
assert result == 15
assert call_count == 1
# Verify the object exists
assert weak_ref() is not None
# Delete the reference and force garbage collection
del obj
gc.collect()
# The object should be garbage collected
assert weak_ref() is None
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment