0
# Functional Programming
1
2
Async versions of functools utilities including reduction, caching, and property decorators designed for async functions and methods. These tools enable functional programming patterns in async contexts.
3
4
## Capabilities
5
6
### Reduction Operations
7
8
Apply functions cumulatively to items in async iterables.
9
10
```python { .api }
11
async def reduce(function, iterable, initial=None):
12
"""
13
Apply function cumulatively to items in iterable.
14
15
Parameters:
16
- function: Callable[[T1, T2], T1] - Binary function to apply
17
- iterable: AnyIterable[T2] - Iterable to reduce
18
- initial: T1, optional - Initial value
19
20
Returns:
21
T1 - Final accumulated value
22
23
Raises:
24
TypeError - If iterable is empty and no initial value provided
25
"""
26
```
27
28
Usage example:
29
```python
30
async def reduction_example():
31
async def numbers():
32
for i in range(1, 6):
33
yield i
34
35
# Calculate factorial: 1 * 2 * 3 * 4 * 5
36
factorial = await reduce(lambda x, y: x * y, numbers()) # 120
37
38
# Sum with initial value
39
total = await reduce(lambda x, y: x + y, numbers(), 10) # 25
40
```
41
42
### Caching Decorators
43
44
Memoization decorators for async functions with LRU (Least Recently Used) eviction.
45
46
```python { .api }
47
def lru_cache(maxsize=128, typed=False):
48
"""
49
LRU cache decorator for async functions.
50
51
Parameters:
52
- maxsize: int or None - Maximum cache size (None for unlimited)
53
- typed: bool - If True, cache separate entries for different argument types
54
55
Returns:
56
Decorator function that wraps async callables with caching
57
58
Usage:
59
@lru_cache(maxsize=256)
60
async def expensive_async_operation(param):
61
# ... expensive computation
62
return result
63
"""
64
65
def cache(user_function):
66
"""
67
Simple cache decorator for async functions (equivalent to lru_cache(maxsize=None)).
68
69
Parameters:
70
- user_function: Async callable to cache
71
72
Returns:
73
Cached version of the function
74
75
Usage:
76
@cache
77
async def compute_value(x, y):
78
# ... expensive computation
79
return result
80
"""
81
```
82
83
### Cached Properties
84
85
Property descriptors that cache async computation results.
86
87
```python { .api }
88
class CachedProperty:
89
"""
90
Cached property descriptor for async getters.
91
"""
92
93
def __init__(self, getter, lock_type=...):
94
"""
95
Parameters:
96
- getter: Callable[[T], Awaitable[R]] - Async getter function
97
- lock_type: type[AsyncContextManager[Any]], optional - Lock type for thread safety
98
"""
99
100
def __set_name__(self, owner, name):
101
"""Set the attribute name on the owner class."""
102
103
def __get__(self, instance, owner):
104
"""
105
Get cached property value.
106
107
Returns:
108
Awaitable[R] - Awaitable that resolves to cached value
109
"""
110
111
def __set__(self, instance, value):
112
"""Set cached value directly."""
113
114
def __del__(self, instance):
115
"""Delete cached value."""
116
117
def cached_property(getter):
118
"""
119
Decorator to create cached property from async getter.
120
121
Parameters:
122
- getter: Callable[[T], Awaitable[R]] - Async getter function
123
124
Returns:
125
CachedProperty[T, R] - Cached property descriptor
126
127
Usage:
128
class MyClass:
129
@cached_property
130
async def expensive_property(self):
131
# ... expensive async computation
132
return result
133
"""
134
```
135
136
## Usage Examples
137
138
### Caching Expensive Operations
139
```python
140
from asyncstdlib import lru_cache, cache
141
import aiohttp
142
143
@lru_cache(maxsize=100)
144
async def fetch_user_data(user_id):
145
"""Cache user data fetches to avoid repeated API calls."""
146
async with aiohttp.ClientSession() as session:
147
async with session.get(f"/api/users/{user_id}") as response:
148
return await response.json()
149
150
@cache
151
async def compute_fibonacci(n):
152
"""Cache fibonacci computation (unlimited cache size)."""
153
if n <= 1:
154
return n
155
# Note: This creates a simple recursive cache
156
return await compute_fibonacci(n-1) + await compute_fibonacci(n-2)
157
158
async def caching_example():
159
# First call fetches from API
160
user1 = await fetch_user_data("123")
161
162
# Second call returns cached result
163
user1_cached = await fetch_user_data("123")
164
165
# Fibonacci with caching
166
result = await compute_fibonacci(10) # 55
167
```
168
169
### Cached Properties
170
```python
171
import asyncio
172
from asyncstdlib import cached_property
173
174
class DataProcessor:
175
def __init__(self, data_source):
176
self.data_source = data_source
177
178
@cached_property
179
async def processed_data(self):
180
"""Expensive data processing, cached after first access."""
181
print("Processing data...") # Only prints once
182
await asyncio.sleep(1) # Simulate expensive operation
183
return [item.upper() for item in self.data_source]
184
185
@cached_property
186
async def data_summary(self):
187
"""Summary depends on processed data."""
188
data = await self.processed_data # Uses cached value
189
return {
190
"count": len(data),
191
"first": data[0] if data else None,
192
"last": data[-1] if data else None
193
}
194
195
async def property_example():
196
processor = DataProcessor(["hello", "world", "async"])
197
198
# First access processes and caches
199
data1 = await processor.processed_data # "Processing data..." printed
200
201
# Second access uses cache
202
data2 = await processor.processed_data # No print, returns cached value
203
204
# Summary uses cached processed_data
205
summary = await processor.data_summary
206
print(summary) # {'count': 3, 'first': 'HELLO', 'last': 'ASYNC'}
207
```
208
209
### Advanced Reduction Operations
210
```python
211
async def advanced_reduction():
212
async def transactions():
213
data = [
214
{"type": "deposit", "amount": 100},
215
{"type": "withdrawal", "amount": 30},
216
{"type": "deposit", "amount": 50},
217
{"type": "withdrawal", "amount": 20}
218
]
219
for tx in data:
220
yield tx
221
222
# Calculate running balance
223
def apply_transaction(balance, transaction):
224
if transaction["type"] == "deposit":
225
return balance + transaction["amount"]
226
else:
227
return balance - transaction["amount"]
228
229
final_balance = await reduce(apply_transaction, transactions(), 0)
230
print(f"Final balance: ${final_balance}") # Final balance: $100
231
232
# Find maximum value with custom comparison
233
async def values():
234
for x in [3, 1, 4, 1, 5, 9, 2, 6]:
235
yield x
236
237
maximum = await reduce(lambda a, b: a if a > b else b, values())
238
print(f"Maximum: {maximum}") # Maximum: 9
239
```
240
241
### Custom Lock Types for Thread Safety
242
```python
243
import asyncio
244
from asyncstdlib import cached_property
245
246
class ThreadSafeProcessor:
247
@cached_property(asyncio.Lock) # Use asyncio.Lock for thread safety
248
async def shared_resource(self):
249
"""Thread-safe cached property."""
250
await asyncio.sleep(0.1)
251
return "computed value"
252
253
# For trio users:
254
# @cached_property(trio.Lock)
255
# async def trio_safe_property(self): ...
256
```