0
# Function and Concurrency Utilities
1
2
Function manipulation, memoization, and enhanced concurrency tools for improving performance and managing function behavior.
3
4
## Capabilities
5
6
### Function Utilities
7
8
Basic function manipulation and utility functions.
9
10
```python { .api }
11
def identity(arg):
12
"""
13
Identity function that returns input unchanged.
14
15
Args:
16
arg: Any input value
17
18
Returns:
19
The same value passed as input
20
21
Note:
22
Useful as default function argument or for functional programming
23
"""
24
25
def inject_method(class_, func, name=None):
26
"""
27
Dynamically inject method into class.
28
29
Args:
30
class_: Target class to modify
31
func: Function to inject as method
32
name (str): Method name (uses func.__name__ if None)
33
34
Returns:
35
The modified class
36
"""
37
38
def compatible(func, args, kwargs):
39
"""
40
Check if function is compatible with given arguments.
41
42
Args:
43
func: Function to check
44
args: Positional arguments
45
kwargs: Keyword arguments
46
47
Returns:
48
bool: True if function can be called with given arguments
49
"""
50
```
51
52
### Memoization
53
54
Decorators for caching function results to improve performance.
55
56
```python { .api }
57
def memoize(func=None, **kwargs):
58
"""
59
Memoize function results for performance optimization.
60
61
Args:
62
func: Function to memoize (when used as @memoize)
63
**kwargs: Memoization options
64
65
Returns:
66
Memoized function
67
68
Usage:
69
@ub.memoize
70
def expensive_function(x):
71
return x ** 2
72
"""
73
74
def memoize_method(func=None, **kwargs):
75
"""
76
Memoize instance method results.
77
78
Args:
79
func: Method to memoize
80
**kwargs: Memoization options
81
82
Returns:
83
Memoized method
84
85
Usage:
86
class MyClass:
87
@ub.memoize_method
88
def expensive_method(self, x):
89
return x ** 2
90
"""
91
92
def memoize_property(func=None, **kwargs):
93
"""
94
Memoize property computation.
95
96
Args:
97
func: Property getter to memoize
98
**kwargs: Memoization options
99
100
Returns:
101
Memoized property
102
103
Usage:
104
class MyClass:
105
@ub.memoize_property
106
def expensive_property(self):
107
return expensive_computation()
108
"""
109
```
110
111
### Concurrency
112
113
Enhanced concurrency tools for parallel execution and job management.
114
115
```python { .api }
116
class Executor:
117
"""
118
Enhanced executor interface wrapping concurrent.futures.
119
Provides simplified API for parallel execution.
120
"""
121
def __init__(self, mode='thread', max_workers=None, **kwargs):
122
"""
123
Args:
124
mode (str): Execution mode ('thread', 'process')
125
max_workers (int): Maximum number of workers
126
**kwargs: Additional executor options
127
"""
128
129
def submit(self, func, *args, **kwargs):
130
"""Submit function for execution"""
131
132
def map(self, func, *iterables, **kwargs):
133
"""Map function over iterables in parallel"""
134
135
def __enter__(self): ...
136
def __exit__(self, exc_type, exc_val, exc_tb): ...
137
138
class JobPool:
139
"""
140
Job pool for managing concurrent tasks with progress tracking.
141
"""
142
def __init__(self, mode='thread', max_workers=None): ...
143
144
def submit(self, func, *args, **kwargs):
145
"""Submit job to pool"""
146
147
def collect(self, show_progress=True):
148
"""Collect all job results"""
149
150
def __enter__(self): ...
151
def __exit__(self, exc_type, exc_val, exc_tb): ...
152
```
153
154
## Usage Examples
155
156
### Basic Function Utilities
157
158
```python
159
import ubelt as ub
160
161
# Identity function
162
result = ub.identity(42)
163
print(result) # 42
164
165
# Useful in functional programming
166
data = [1, 2, 3, 4, 5]
167
filtered = filter(lambda x: x > 3, data)
168
# Use identity when no transformation needed
169
result = list(map(ub.identity, filtered))
170
print(result) # [4, 5]
171
172
# Default function argument
173
def process_data(data, transform=ub.identity):
174
"""Process data with optional transformation"""
175
return [transform(item) for item in data]
176
177
# No transformation
178
result1 = process_data([1, 2, 3])
179
print(result1) # [1, 2, 3]
180
181
# With transformation
182
result2 = process_data([1, 2, 3], transform=lambda x: x * 2)
183
print(result2) # [2, 4, 6]
184
```
185
186
### Dynamic Method Injection
187
188
```python
189
import ubelt as ub
190
191
# Define a class
192
class Calculator:
193
def __init__(self, value=0):
194
self.value = value
195
196
def add(self, x):
197
self.value += x
198
return self
199
200
# Define function to inject
201
def multiply(self, x):
202
self.value *= x
203
return self
204
205
def power(self, exp):
206
self.value = self.value ** exp
207
return self
208
209
# Inject methods dynamically
210
ub.inject_method(Calculator, multiply)
211
ub.inject_method(Calculator, power)
212
213
# Use the enhanced class
214
calc = Calculator(5)
215
result = calc.add(3).multiply(2).power(2)
216
print(calc.value) # ((5 + 3) * 2) ** 2 = 256
217
```
218
219
### Function Compatibility Checking
220
221
```python
222
import ubelt as ub
223
224
def func_with_args(a, b, c=10):
225
return a + b + c
226
227
def func_with_kwargs(x, **kwargs):
228
return x + sum(kwargs.values())
229
230
# Check compatibility
231
args1 = (1, 2)
232
kwargs1 = {'c': 5}
233
compatible1 = ub.compatible(func_with_args, args1, kwargs1)
234
print(f"Compatible with args {args1}, kwargs {kwargs1}: {compatible1}") # True
235
236
args2 = (1,) # Missing required argument 'b'
237
compatible2 = ub.compatible(func_with_args, args2, {})
238
print(f"Compatible with args {args2}: {compatible2}") # False
239
240
# Use for safe function calls
241
def safe_call(func, *args, **kwargs):
242
if ub.compatible(func, args, kwargs):
243
return func(*args, **kwargs)
244
else:
245
return None
246
247
result = safe_call(func_with_args, 1, 2, c=3)
248
print(f"Safe call result: {result}") # 6
249
```
250
251
### Memoization
252
253
```python
254
import ubelt as ub
255
import time
256
257
# Basic function memoization
258
@ub.memoize
259
def fibonacci(n):
260
if n <= 1:
261
return n
262
time.sleep(0.01) # Simulate expensive computation
263
return fibonacci(n-1) + fibonacci(n-2)
264
265
# First call is slow
266
start_time = time.time()
267
result1 = fibonacci(10)
268
time1 = time.time() - start_time
269
print(f"First call: {result1} in {time1:.3f}s")
270
271
# Second call is fast (memoized)
272
start_time = time.time()
273
result2 = fibonacci(10)
274
time2 = time.time() - start_time
275
print(f"Memoized call: {result2} in {time2:.6f}s")
276
277
# Method memoization
278
class DataProcessor:
279
def __init__(self, data):
280
self.data = data
281
282
@ub.memoize_method
283
def expensive_analysis(self, threshold):
284
"""Expensive analysis that should be cached"""
285
print(f"Performing analysis with threshold {threshold}")
286
time.sleep(0.1) # Simulate work
287
return sum(x for x in self.data if x > threshold)
288
289
processor = DataProcessor([1, 5, 10, 15, 20, 25])
290
result1 = processor.expensive_analysis(10) # Slow first call
291
print(f"Analysis result: {result1}")
292
293
result2 = processor.expensive_analysis(10) # Fast second call
294
print(f"Cached result: {result2}")
295
```
296
297
### Property Memoization
298
299
```python
300
import ubelt as ub
301
import time
302
303
class ExpensiveCalculation:
304
def __init__(self, data):
305
self.data = data
306
307
@ub.memoize_property
308
def mean(self):
309
"""Expensive mean calculation"""
310
print("Computing mean...")
311
time.sleep(0.1) # Simulate expensive computation
312
return sum(self.data) / len(self.data)
313
314
@ub.memoize_property
315
def variance(self):
316
"""Expensive variance calculation"""
317
print("Computing variance...")
318
time.sleep(0.1)
319
mean_val = self.mean # This will use memoized mean
320
return sum((x - mean_val) ** 2 for x in self.data) / len(self.data)
321
322
calc = ExpensiveCalculation([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
323
print(f"Mean: {calc.mean}") # Computes and caches
324
print(f"Variance: {calc.variance}") # Computes and caches (reuses mean)
325
print(f"Mean again: {calc.mean}") # Uses cache
326
```
327
328
### Parallel Execution
329
330
```python
331
import ubelt as ub
332
import time
333
334
def slow_function(x):
335
"""Simulate slow computation"""
336
time.sleep(0.1)
337
return x ** 2
338
339
# Sequential execution
340
start_time = time.time()
341
results_sequential = [slow_function(i) for i in range(10)]
342
sequential_time = time.time() - start_time
343
print(f"Sequential: {sequential_time:.3f}s")
344
345
# Parallel execution with Executor
346
start_time = time.time()
347
with ub.Executor(mode='thread', max_workers=4) as executor:
348
results_parallel = list(executor.map(slow_function, range(10)))
349
parallel_time = time.time() - start_time
350
print(f"Parallel: {parallel_time:.3f}s")
351
print(f"Speedup: {sequential_time/parallel_time:.2f}x")
352
```
353
354
### Job Pool with Progress
355
356
```python
357
import ubelt as ub
358
import time
359
import random
360
361
def variable_work(task_id):
362
"""Task with variable execution time"""
363
work_time = random.uniform(0.1, 0.5)
364
time.sleep(work_time)
365
return f"Task {task_id} completed in {work_time:.2f}s"
366
367
# Submit jobs to pool
368
with ub.JobPool(mode='thread', max_workers=3) as pool:
369
# Submit multiple jobs
370
for i in range(10):
371
pool.submit(variable_work, i)
372
373
# Collect results with progress bar
374
results = pool.collect(show_progress=True)
375
376
print("Results:")
377
for result in results:
378
print(f" {result}")
379
```
380
381
### Advanced Concurrent Patterns
382
383
```python
384
import ubelt as ub
385
import time
386
import requests
387
from concurrent.futures import as_completed
388
389
def fetch_url(url):
390
"""Fetch URL with error handling"""
391
try:
392
response = requests.get(url, timeout=5)
393
return {'url': url, 'status': response.status_code, 'size': len(response.content)}
394
except Exception as e:
395
return {'url': url, 'error': str(e)}
396
397
# URLs to fetch
398
urls = [
399
'https://httpbin.org/delay/1',
400
'https://httpbin.org/delay/2',
401
'https://httpbin.org/status/200',
402
'https://httpbin.org/status/404',
403
'https://httpbin.org/json'
404
]
405
406
# Fetch URLs in parallel with custom handling
407
with ub.Executor(mode='thread', max_workers=3) as executor:
408
# Submit all tasks
409
future_to_url = {executor.submit(fetch_url, url): url for url in urls}
410
411
# Process results as they complete
412
for future in ub.ProgIter(as_completed(future_to_url), total=len(urls), desc='Fetching'):
413
url = future_to_url[future]
414
try:
415
result = future.result()
416
if 'error' in result:
417
print(f"Error fetching {url}: {result['error']}")
418
else:
419
print(f"Success {url}: {result['status']} ({result['size']} bytes)")
420
except Exception as e:
421
print(f"Exception for {url}: {e}")
422
```
423
424
### Memoization with Custom Cache Control
425
426
```python
427
import ubelt as ub
428
import time
429
430
# Memoization with cache size limit
431
@ub.memoize
432
def expensive_function(x, y):
433
print(f"Computing {x} + {y}")
434
time.sleep(0.1)
435
return x + y
436
437
# Clear cache when needed
438
def reset_cache():
439
expensive_function.cache_clear()
440
441
# Use memoized function
442
result1 = expensive_function(1, 2) # Computes
443
result2 = expensive_function(1, 2) # Uses cache
444
result3 = expensive_function(3, 4) # Computes
445
446
print(f"Cache info: {expensive_function.cache_info()}")
447
448
# Clear and recompute
449
reset_cache()
450
result4 = expensive_function(1, 2) # Computes again
451
```