or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/pypi-cached-property

A decorator for caching properties in classes.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/cached-property@2.0.x

To install, run

npx @tessl/cli install tessl/pypi-cached-property@2.0.0

index.mddocs/

Cached Property

A decorator for caching properties in classes that provides comprehensive property caching functionality for Python classes. The library enables developers to cache expensive property computations and avoid redundant calculations through multiple caching strategies including basic caching, thread-safe caching, time-based expiration with TTL support, and async/await compatibility.

Package Information

  • Package Name: cached-property
  • Language: Python
  • Installation: pip install cached-property
  • Python Requirements: >=3.8
  • License: BSD

Core Imports

from cached_property import cached_property

For thread-safe environments:

from cached_property import threaded_cached_property

For time-based cache expiration:

from cached_property import cached_property_with_ttl

For both thread safety and TTL:

from cached_property import threaded_cached_property_with_ttl

Alternative alias imports:

from cached_property import cached_property_ttl, timed_cached_property
from cached_property import threaded_cached_property_ttl, timed_threaded_cached_property

Basic Usage

from cached_property import cached_property

class DataProcessor:
    def __init__(self, data):
        self.data = data
    
    @cached_property
    def expensive_computation(self):
        # This computation only runs once per instance
        result = sum(x ** 2 for x in self.data)
        print("Computing...")  # This will only print once
        return result

# Usage
processor = DataProcessor([1, 2, 3, 4, 5])
print(processor.expensive_computation)  # Computes and caches
print(processor.expensive_computation)  # Returns cached value

# Cache invalidation
del processor.expensive_computation  # or del processor.__dict__['expensive_computation']
print(processor.expensive_computation)  # Computes again

Capabilities

Basic Property Caching

The fundamental cached property decorator that computes a property value once per instance and then replaces itself with an ordinary attribute. Supports both synchronous and asynchronous property functions.

class cached_property:
    """
    A property that is only computed once per instance and then replaces itself
    with an ordinary attribute. Deleting the attribute resets the property.
    """
    
    def __init__(self, func):
        """
        Initialize the cached property decorator.
        
        Args:
            func: The function to be cached (can be sync or async)
        """
    
    def __get__(self, obj, cls):
        """
        Descriptor protocol implementation.
        
        Args:
            obj: The instance accessing the property
            cls: The owner class
            
        Returns:
            The cached property value or a coroutine wrapper for async functions
        """

Usage Example:

from cached_property import cached_property
import asyncio

class Example:
    @cached_property
    def regular_property(self):
        return "computed once"
    
    @cached_property
    async def async_property(self):
        await asyncio.sleep(0.1)  # Simulate async work
        return "async computed once"

# Regular usage
example = Example()
print(example.regular_property)  # Computes
print(example.regular_property)  # Cached

# Async usage
async def main():
    example = Example()
    result = await example.async_property  # Computes and caches
    result2 = await example.async_property  # Returns cached future
    
asyncio.run(main())

Thread-Safe Property Caching

A thread-safe version of cached_property for environments where multiple threads might concurrently try to access the property. Uses reentrant locking to ensure only one computation occurs even with concurrent access.

class threaded_cached_property:
    """
    A cached_property version for use in environments where multiple threads
    might concurrently try to access the property.
    """
    
    def __init__(self, func):
        """
        Initialize the thread-safe cached property decorator.
        
        Args:
            func: The function to be cached
        """
    
    def __get__(self, obj, cls):
        """
        Thread-safe descriptor protocol implementation.
        
        Args:
            obj: The instance accessing the property
            cls: The owner class
            
        Returns:
            The cached property value
        """

Usage Example:

from cached_property import threaded_cached_property
from threading import Thread
import time

class ThreadSafeExample:
    def __init__(self):
        self.compute_count = 0
    
    @threaded_cached_property
    def thread_safe_property(self):
        time.sleep(0.1)  # Simulate work
        self.compute_count += 1
        return f"computed {self.compute_count} times"

example = ThreadSafeExample()

# Multiple threads accessing simultaneously
threads = []
for i in range(10):
    thread = Thread(target=lambda: print(example.thread_safe_property))
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

# Only computed once despite concurrent access
assert example.compute_count == 1

Time-Based Cache Expiration (TTL)

Property caching with time-to-live (TTL) expiration support. The cached value expires after a specified time period, forcing recomputation on next access. Supports manual cache invalidation and setting.

class cached_property_with_ttl:
    """
    A property that is only computed once per instance and then replaces itself
    with an ordinary attribute. Setting the ttl to a number expresses how long
    the property will last before being timed out.
    """
    
    def __init__(self, ttl=None):
        """
        Initialize the TTL cached property decorator.
        
        Args:
            ttl (float, optional): Time-to-live in seconds. If None, cache never expires.
        """
    
    def __call__(self, func):
        """
        Allow usage as decorator with parameters.
        
        Args:
            func: The function to be cached
            
        Returns:
            self
        """
    
    def __get__(self, obj, cls):
        """
        TTL-aware descriptor protocol implementation.
        
        Args:
            obj: The instance accessing the property
            cls: The owner class
            
        Returns:
            The cached property value (recomputed if TTL expired)
        """
    
    def __delete__(self, obj):
        """
        Manual cache invalidation.
        
        Args:
            obj: The instance to clear cache for
        """
    
    def __set__(self, obj, value):
        """
        Manual cache setting.
        
        Args:
            obj: The instance to set cache for
            value: The value to cache
        """

Usage Example:

from cached_property import cached_property_with_ttl
import time
import random

class TTLExample:
    @cached_property_with_ttl(ttl=2)  # Cache expires after 2 seconds
    def random_value(self):
        return random.randint(1, 100)
    
    # Can also be used without TTL (cache never expires)
    @cached_property_with_ttl
    def permanent_cache(self):
        return "never expires"

example = TTLExample()

# Initial computation
value1 = example.random_value
print(f"First: {value1}")

# Within TTL - returns cached value
value2 = example.random_value
print(f"Second: {value2}")  # Same as value1

# Wait for TTL expiration
time.sleep(3)

# After TTL - recomputes
value3 = example.random_value
print(f"Third: {value3}")   # Different from value1

# Manual cache control
example.random_value = 999  # Set cache manually
print(example.random_value)  # Returns 999

del example.random_value    # Clear cache manually
print(example.random_value)  # Computes new value

Thread-Safe TTL Property Caching

Combines thread safety with TTL expiration. Uses reentrant locking to ensure thread-safe access while supporting time-based cache expiration. Inherits all TTL functionality with thread safety guarantees.

class threaded_cached_property_with_ttl(cached_property_with_ttl):
    """
    A cached_property version for use in environments where multiple threads
    might concurrently try to access the property.
    """
    
    def __init__(self, ttl=None):
        """
        Initialize the thread-safe TTL cached property decorator.
        
        Args:
            ttl (float, optional): Time-to-live in seconds. If None, cache never expires.
        """
    
    def __get__(self, obj, cls):
        """
        Thread-safe TTL-aware descriptor protocol implementation.
        
        Args:
            obj: The instance accessing the property
            cls: The owner class
            
        Returns:
            The cached property value (recomputed if TTL expired)
        """

Usage Example:

from cached_property import threaded_cached_property_with_ttl
from threading import Thread
import time

class ThreadSafeTTLExample:
    def __init__(self):
        self.access_count = 0
    
    @threaded_cached_property_with_ttl(ttl=5)  # 5-second TTL
    def thread_safe_ttl_property(self):
        self.access_count += 1
        return f"computed {self.access_count} times at {time.time()}"

example = ThreadSafeTTLExample()

# Multiple threads accessing within TTL
def access_property():
    return example.thread_safe_ttl_property

threads = []
for i in range(5):
    thread = Thread(target=access_property)
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

# Only computed once despite concurrent access
assert example.access_count == 1

Convenience Aliases

The library provides convenient aliases for easier usage:

# Aliases for cached_property_with_ttl
cached_property_ttl = cached_property_with_ttl
timed_cached_property = cached_property_with_ttl

# Aliases for threaded_cached_property_with_ttl  
threaded_cached_property_ttl = threaded_cached_property_with_ttl
timed_threaded_cached_property = threaded_cached_property_with_ttl

Usage with Aliases:

from cached_property import cached_property_ttl, timed_cached_property

class AliasExample:
    @cached_property_ttl(ttl=10)
    def using_ttl_alias(self):
        return "cached with alias"
    
    @timed_cached_property(ttl=5)
    def using_timed_alias(self):
        return "cached with timed alias"

Cache Management

Cache Invalidation

Remove cached values to force recomputation:

# Method 1: Delete the property directly (works with all cached_property variants)
del instance.property_name

# Method 2: Delete from instance dictionary (works with basic cached_property only)
del instance.__dict__['property_name']

# Method 3: For TTL variants, use the __delete__ method
# (This is called automatically when using del instance.property_name)

Cache Inspection

Access cached values without triggering computation:

class Inspector:
    @cached_property
    def my_property(self):
        return "computed value"

inspector = Inspector()

# Check if property is cached
if 'my_property' in inspector.__dict__:
    cached_value = inspector.__dict__['my_property']
    print(f"Cached: {cached_value}")
else:
    print("Not cached yet")

# For TTL variants, cached values are stored as (value, timestamp) tuples
class TTLInspector:
    @cached_property_with_ttl(ttl=60)
    def ttl_property(self):
        return "ttl value"

ttl_inspector = TTLInspector()
_ = ttl_inspector.ttl_property  # Trigger caching

if 'ttl_property' in ttl_inspector.__dict__:
    value, timestamp = ttl_inspector.__dict__['ttl_property']
    print(f"Value: {value}, Cached at: {timestamp}")

Error Handling

The cached property decorators preserve and cache exceptions:

from cached_property import cached_property

class ErrorExample:
    @cached_property
    def failing_property(self):
        raise ValueError("This always fails")

example = ErrorExample()

try:
    _ = example.failing_property
except ValueError:
    print("Exception occurred and was cached")

# Subsequent access returns the same exception
try:
    _ = example.failing_property  # Same exception, no recomputation
except ValueError:
    print("Same cached exception")

# Clear cache to retry
del example.failing_property

Integration with Async/Await

The basic cached_property decorator automatically detects and handles coroutine functions:

import asyncio
from cached_property import cached_property

class AsyncExample:
    @cached_property
    async def async_data(self):
        # Simulate async operation
        await asyncio.sleep(0.1)
        return "async result"

async def main():
    example = AsyncExample()
    
    # First access - computes and caches a Future
    result1 = await example.async_data
    
    # Subsequent access - returns the same Future
    result2 = await example.async_data
    
    print(f"Result: {result1}")  # "async result"
    assert result1 == result2

asyncio.run(main())

Note: Async support is only available with the basic cached_property decorator. The threaded variants are not compatible with asyncio due to thread safety concerns with event loops.