or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md

index.mddocs/

0

# Cached Property

1

2

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.

3

4

## Package Information

5

6

- **Package Name**: cached-property

7

- **Language**: Python

8

- **Installation**: `pip install cached-property`

9

- **Python Requirements**: `>=3.8`

10

- **License**: BSD

11

12

## Core Imports

13

14

```python

15

from cached_property import cached_property

16

```

17

18

For thread-safe environments:

19

20

```python

21

from cached_property import threaded_cached_property

22

```

23

24

For time-based cache expiration:

25

26

```python

27

from cached_property import cached_property_with_ttl

28

```

29

30

For both thread safety and TTL:

31

32

```python

33

from cached_property import threaded_cached_property_with_ttl

34

```

35

36

Alternative alias imports:

37

38

```python

39

from cached_property import cached_property_ttl, timed_cached_property

40

from cached_property import threaded_cached_property_ttl, timed_threaded_cached_property

41

```

42

43

## Basic Usage

44

45

```python

46

from cached_property import cached_property

47

48

class DataProcessor:

49

def __init__(self, data):

50

self.data = data

51

52

@cached_property

53

def expensive_computation(self):

54

# This computation only runs once per instance

55

result = sum(x ** 2 for x in self.data)

56

print("Computing...") # This will only print once

57

return result

58

59

# Usage

60

processor = DataProcessor([1, 2, 3, 4, 5])

61

print(processor.expensive_computation) # Computes and caches

62

print(processor.expensive_computation) # Returns cached value

63

64

# Cache invalidation

65

del processor.expensive_computation # or del processor.__dict__['expensive_computation']

66

print(processor.expensive_computation) # Computes again

67

```

68

69

## Capabilities

70

71

### Basic Property Caching

72

73

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.

74

75

```python { .api }

76

class cached_property:

77

"""

78

A property that is only computed once per instance and then replaces itself

79

with an ordinary attribute. Deleting the attribute resets the property.

80

"""

81

82

def __init__(self, func):

83

"""

84

Initialize the cached property decorator.

85

86

Args:

87

func: The function to be cached (can be sync or async)

88

"""

89

90

def __get__(self, obj, cls):

91

"""

92

Descriptor protocol implementation.

93

94

Args:

95

obj: The instance accessing the property

96

cls: The owner class

97

98

Returns:

99

The cached property value or a coroutine wrapper for async functions

100

"""

101

```

102

103

**Usage Example:**

104

105

```python

106

from cached_property import cached_property

107

import asyncio

108

109

class Example:

110

@cached_property

111

def regular_property(self):

112

return "computed once"

113

114

@cached_property

115

async def async_property(self):

116

await asyncio.sleep(0.1) # Simulate async work

117

return "async computed once"

118

119

# Regular usage

120

example = Example()

121

print(example.regular_property) # Computes

122

print(example.regular_property) # Cached

123

124

# Async usage

125

async def main():

126

example = Example()

127

result = await example.async_property # Computes and caches

128

result2 = await example.async_property # Returns cached future

129

130

asyncio.run(main())

131

```

132

133

### Thread-Safe Property Caching

134

135

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.

136

137

```python { .api }

138

class threaded_cached_property:

139

"""

140

A cached_property version for use in environments where multiple threads

141

might concurrently try to access the property.

142

"""

143

144

def __init__(self, func):

145

"""

146

Initialize the thread-safe cached property decorator.

147

148

Args:

149

func: The function to be cached

150

"""

151

152

def __get__(self, obj, cls):

153

"""

154

Thread-safe descriptor protocol implementation.

155

156

Args:

157

obj: The instance accessing the property

158

cls: The owner class

159

160

Returns:

161

The cached property value

162

"""

163

```

164

165

**Usage Example:**

166

167

```python

168

from cached_property import threaded_cached_property

169

from threading import Thread

170

import time

171

172

class ThreadSafeExample:

173

def __init__(self):

174

self.compute_count = 0

175

176

@threaded_cached_property

177

def thread_safe_property(self):

178

time.sleep(0.1) # Simulate work

179

self.compute_count += 1

180

return f"computed {self.compute_count} times"

181

182

example = ThreadSafeExample()

183

184

# Multiple threads accessing simultaneously

185

threads = []

186

for i in range(10):

187

thread = Thread(target=lambda: print(example.thread_safe_property))

188

threads.append(thread)

189

thread.start()

190

191

for thread in threads:

192

thread.join()

193

194

# Only computed once despite concurrent access

195

assert example.compute_count == 1

196

```

197

198

### Time-Based Cache Expiration (TTL)

199

200

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.

201

202

```python { .api }

203

class cached_property_with_ttl:

204

"""

205

A property that is only computed once per instance and then replaces itself

206

with an ordinary attribute. Setting the ttl to a number expresses how long

207

the property will last before being timed out.

208

"""

209

210

def __init__(self, ttl=None):

211

"""

212

Initialize the TTL cached property decorator.

213

214

Args:

215

ttl (float, optional): Time-to-live in seconds. If None, cache never expires.

216

"""

217

218

def __call__(self, func):

219

"""

220

Allow usage as decorator with parameters.

221

222

Args:

223

func: The function to be cached

224

225

Returns:

226

self

227

"""

228

229

def __get__(self, obj, cls):

230

"""

231

TTL-aware descriptor protocol implementation.

232

233

Args:

234

obj: The instance accessing the property

235

cls: The owner class

236

237

Returns:

238

The cached property value (recomputed if TTL expired)

239

"""

240

241

def __delete__(self, obj):

242

"""

243

Manual cache invalidation.

244

245

Args:

246

obj: The instance to clear cache for

247

"""

248

249

def __set__(self, obj, value):

250

"""

251

Manual cache setting.

252

253

Args:

254

obj: The instance to set cache for

255

value: The value to cache

256

"""

257

```

258

259

**Usage Example:**

260

261

```python

262

from cached_property import cached_property_with_ttl

263

import time

264

import random

265

266

class TTLExample:

267

@cached_property_with_ttl(ttl=2) # Cache expires after 2 seconds

268

def random_value(self):

269

return random.randint(1, 100)

270

271

# Can also be used without TTL (cache never expires)

272

@cached_property_with_ttl

273

def permanent_cache(self):

274

return "never expires"

275

276

example = TTLExample()

277

278

# Initial computation

279

value1 = example.random_value

280

print(f"First: {value1}")

281

282

# Within TTL - returns cached value

283

value2 = example.random_value

284

print(f"Second: {value2}") # Same as value1

285

286

# Wait for TTL expiration

287

time.sleep(3)

288

289

# After TTL - recomputes

290

value3 = example.random_value

291

print(f"Third: {value3}") # Different from value1

292

293

# Manual cache control

294

example.random_value = 999 # Set cache manually

295

print(example.random_value) # Returns 999

296

297

del example.random_value # Clear cache manually

298

print(example.random_value) # Computes new value

299

```

300

301

### Thread-Safe TTL Property Caching

302

303

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.

304

305

```python { .api }

306

class threaded_cached_property_with_ttl(cached_property_with_ttl):

307

"""

308

A cached_property version for use in environments where multiple threads

309

might concurrently try to access the property.

310

"""

311

312

def __init__(self, ttl=None):

313

"""

314

Initialize the thread-safe TTL cached property decorator.

315

316

Args:

317

ttl (float, optional): Time-to-live in seconds. If None, cache never expires.

318

"""

319

320

def __get__(self, obj, cls):

321

"""

322

Thread-safe TTL-aware descriptor protocol implementation.

323

324

Args:

325

obj: The instance accessing the property

326

cls: The owner class

327

328

Returns:

329

The cached property value (recomputed if TTL expired)

330

"""

331

```

332

333

**Usage Example:**

334

335

```python

336

from cached_property import threaded_cached_property_with_ttl

337

from threading import Thread

338

import time

339

340

class ThreadSafeTTLExample:

341

def __init__(self):

342

self.access_count = 0

343

344

@threaded_cached_property_with_ttl(ttl=5) # 5-second TTL

345

def thread_safe_ttl_property(self):

346

self.access_count += 1

347

return f"computed {self.access_count} times at {time.time()}"

348

349

example = ThreadSafeTTLExample()

350

351

# Multiple threads accessing within TTL

352

def access_property():

353

return example.thread_safe_ttl_property

354

355

threads = []

356

for i in range(5):

357

thread = Thread(target=access_property)

358

threads.append(thread)

359

thread.start()

360

361

for thread in threads:

362

thread.join()

363

364

# Only computed once despite concurrent access

365

assert example.access_count == 1

366

```

367

368

## Convenience Aliases

369

370

The library provides convenient aliases for easier usage:

371

372

```python { .api }

373

# Aliases for cached_property_with_ttl

374

cached_property_ttl = cached_property_with_ttl

375

timed_cached_property = cached_property_with_ttl

376

377

# Aliases for threaded_cached_property_with_ttl

378

threaded_cached_property_ttl = threaded_cached_property_with_ttl

379

timed_threaded_cached_property = threaded_cached_property_with_ttl

380

```

381

382

**Usage with Aliases:**

383

384

```python

385

from cached_property import cached_property_ttl, timed_cached_property

386

387

class AliasExample:

388

@cached_property_ttl(ttl=10)

389

def using_ttl_alias(self):

390

return "cached with alias"

391

392

@timed_cached_property(ttl=5)

393

def using_timed_alias(self):

394

return "cached with timed alias"

395

```

396

397

## Cache Management

398

399

### Cache Invalidation

400

401

Remove cached values to force recomputation:

402

403

```python

404

# Method 1: Delete the property directly (works with all cached_property variants)

405

del instance.property_name

406

407

# Method 2: Delete from instance dictionary (works with basic cached_property only)

408

del instance.__dict__['property_name']

409

410

# Method 3: For TTL variants, use the __delete__ method

411

# (This is called automatically when using del instance.property_name)

412

```

413

414

### Cache Inspection

415

416

Access cached values without triggering computation:

417

418

```python

419

class Inspector:

420

@cached_property

421

def my_property(self):

422

return "computed value"

423

424

inspector = Inspector()

425

426

# Check if property is cached

427

if 'my_property' in inspector.__dict__:

428

cached_value = inspector.__dict__['my_property']

429

print(f"Cached: {cached_value}")

430

else:

431

print("Not cached yet")

432

433

# For TTL variants, cached values are stored as (value, timestamp) tuples

434

class TTLInspector:

435

@cached_property_with_ttl(ttl=60)

436

def ttl_property(self):

437

return "ttl value"

438

439

ttl_inspector = TTLInspector()

440

_ = ttl_inspector.ttl_property # Trigger caching

441

442

if 'ttl_property' in ttl_inspector.__dict__:

443

value, timestamp = ttl_inspector.__dict__['ttl_property']

444

print(f"Value: {value}, Cached at: {timestamp}")

445

```

446

447

## Error Handling

448

449

The cached property decorators preserve and cache exceptions:

450

451

```python

452

from cached_property import cached_property

453

454

class ErrorExample:

455

@cached_property

456

def failing_property(self):

457

raise ValueError("This always fails")

458

459

example = ErrorExample()

460

461

try:

462

_ = example.failing_property

463

except ValueError:

464

print("Exception occurred and was cached")

465

466

# Subsequent access returns the same exception

467

try:

468

_ = example.failing_property # Same exception, no recomputation

469

except ValueError:

470

print("Same cached exception")

471

472

# Clear cache to retry

473

del example.failing_property

474

```

475

476

## Integration with Async/Await

477

478

The basic `cached_property` decorator automatically detects and handles coroutine functions:

479

480

```python

481

import asyncio

482

from cached_property import cached_property

483

484

class AsyncExample:

485

@cached_property

486

async def async_data(self):

487

# Simulate async operation

488

await asyncio.sleep(0.1)

489

return "async result"

490

491

async def main():

492

example = AsyncExample()

493

494

# First access - computes and caches a Future

495

result1 = await example.async_data

496

497

# Subsequent access - returns the same Future

498

result2 = await example.async_data

499

500

print(f"Result: {result1}") # "async result"

501

assert result1 == result2

502

503

asyncio.run(main())

504

```

505

506

**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.