0
# Partial Function Support
1
2
Enhanced partial function implementation with better introspection, documentation generation, and signature handling compared to `functools.partial`. These utilities provide superior user experience for partial function application with proper metadata preservation.
3
4
```python
5
from typing import Callable, Any, TypeVar
6
7
T = TypeVar('T')
8
```
9
10
## Capabilities
11
12
### Enhanced Partial Function
13
14
Improved version of `functools.partial` with better introspection and automatically generated documentation.
15
16
```python { .api }
17
def partial(f: Callable[..., T], *preset_pos_args: Any, **preset_kwargs: Any) -> Callable[..., T]:
18
"""
19
Enhanced functools.partial with better introspection and documentation.
20
21
Parameters:
22
- f: Callable[..., T], function to partially apply
23
- *preset_pos_args: Any, positional arguments to preset/bind
24
- **preset_kwargs: Any, keyword arguments to preset/bind
25
26
Returns:
27
Callable[..., T]: Partial function with enhanced metadata and signature
28
29
The returned function has:
30
- Proper signature reflecting remaining parameters
31
- Auto-generated docstring showing preset values
32
- .func attribute pointing to original function (like functools.partial)
33
- Better introspection support with inspect.signature()
34
35
Raises:
36
ValueError: If preset arguments don't match function signature
37
"""
38
```
39
40
### Partial Function Decorator
41
42
Decorator version of partial function for cleaner syntax when creating partial functions.
43
44
```python { .api }
45
def with_partial(*preset_pos_args: Any, **preset_kwargs: Any) -> Callable[[Callable[..., T]], Callable[..., T]]:
46
"""
47
Decorator to create partial function with preset arguments.
48
49
Parameters:
50
- *preset_pos_args: Any, positional arguments to preset
51
- **preset_kwargs: Any, keyword arguments to preset
52
53
Returns:
54
Callable[[Callable[..., T]], Callable[..., T]]: Decorator that creates partial function
55
"""
56
```
57
58
### Usage Examples
59
60
#### Basic Partial Function Creation
61
62
```python
63
from makefun import partial
64
from inspect import signature
65
66
def multiply(x: int, y: int, z: int = 1) -> int:
67
"""Multiply three numbers together."""
68
return x * y * z
69
70
# Create partial with first argument preset
71
double = partial(multiply, 2)
72
print(signature(double)) # (y: int, z: int = 1) -> int
73
print(double(5)) # 10 (2 * 5 * 1)
74
print(double(3, 4)) # 24 (2 * 3 * 4)
75
76
# Create partial with keyword argument preset
77
multiply_by_10 = partial(multiply, z=10)
78
print(signature(multiply_by_10)) # (x: int, y: int) -> int
79
print(multiply_by_10(2, 3)) # 60 (2 * 3 * 10)
80
```
81
82
#### Enhanced Documentation
83
84
```python
85
from makefun import partial
86
87
def api_request(method: str, url: str, headers: dict = None, timeout: int = 30) -> dict:
88
"""Make an HTTP API request."""
89
return {
90
"method": method,
91
"url": url,
92
"headers": headers or {},
93
"timeout": timeout
94
}
95
96
# Create partial for GET requests
97
get_request = partial(api_request, "GET", timeout=60)
98
99
# Automatic documentation generation
100
print(get_request.__doc__)
101
# <This function is equivalent to 'api_request('GET', url, headers=None, timeout=60)', see original 'api_request' doc below.>
102
# Make an HTTP API request.
103
104
print(get_request.__name__) # "api_request" (preserved from original)
105
```
106
107
#### Multiple Argument Presets
108
109
```python
110
from makefun import partial
111
112
def format_message(template: str, name: str, age: int, city: str = "Unknown") -> str:
113
"""Format a message with user information."""
114
return template.format(name=name, age=age, city=city)
115
116
# Preset template and city
117
welcome_message = partial(format_message, "Welcome {name}! You are {age} years old and live in {city}.", city="New York")
118
119
print(welcome_message("Alice", 25)) # Welcome Alice! You are 25 years old and live in New York.
120
print(welcome_message("Bob", 30, "Chicago")) # Welcome Bob! You are 30 years old and live in Chicago.
121
```
122
123
#### Decorator Syntax
124
125
```python
126
from makefun import with_partial
127
128
# Define base function
129
def compute_score(base: int, multiplier: float, bonus: int = 0) -> float:
130
"""Compute final score with base, multiplier, and bonus."""
131
return (base + bonus) * multiplier
132
133
# Create specialized versions using decorator
134
@with_partial(multiplier=1.5)
135
def boosted_score(base: int, bonus: int = 0) -> float:
136
pass # Implementation replaced by partial
137
138
@with_partial(multiplier=2.0, bonus=10)
139
def premium_score(base: int) -> float:
140
pass # Implementation replaced by partial
141
142
print(boosted_score(100)) # 150.0 (100 * 1.5)
143
print(boosted_score(100, 20)) # 180.0 ((100 + 20) * 1.5)
144
print(premium_score(100)) # 220.0 ((100 + 10) * 2.0)
145
```
146
147
#### Working with Complex Signatures
148
149
```python
150
from makefun import partial
151
from typing import List, Dict, Optional
152
153
def analyze_data(data: List[Dict], method: str, normalize: bool = True,
154
weights: Optional[Dict] = None, *extra_params, **options) -> Dict:
155
"""Analyze data using specified method with various options."""
156
result = {
157
"method": method,
158
"data_count": len(data),
159
"normalized": normalize,
160
"weights": weights,
161
"extra_params": extra_params,
162
"options": options
163
}
164
return result
165
166
# Create partial with method and normalization preset
167
statistical_analysis = partial(analyze_data, method="statistical", normalize=True)
168
169
# Remaining signature handles complex parameters correctly
170
sample_data = [{"value": 1}, {"value": 2}]
171
result = statistical_analysis(sample_data, weights={"a": 0.5}, "param1", "param2",
172
threshold=0.1, debug=True)
173
print(result)
174
```
175
176
#### Generator and Async Function Support
177
178
```python
179
from makefun import partial
180
import asyncio
181
182
def number_generator(start: int, end: int, step: int = 1, prefix: str = "Num"):
183
"""Generate numbers with optional prefix."""
184
for i in range(start, end, step):
185
yield f"{prefix}: {i}"
186
187
# Create partial generator
188
even_numbers = partial(number_generator, step=2, prefix="Even")
189
190
for num in even_numbers(0, 10):
191
print(num) # Even: 0, Even: 2, Even: 4, etc.
192
193
# Async function partial
194
async def fetch_data(url: str, method: str = "GET", timeout: int = 30) -> dict:
195
"""Simulate async data fetching."""
196
await asyncio.sleep(0.1)
197
return {"url": url, "method": method, "timeout": timeout}
198
199
# Create partial async function
200
quick_fetch = partial(fetch_data, timeout=5)
201
result = asyncio.run(quick_fetch("https://api.example.com", "POST"))
202
print(result)
203
```
204
205
#### Parameter Order and Keyword-Only Arguments
206
207
```python
208
from makefun import partial
209
210
def complex_func(a: int, b: str, c: float = 1.0, *, d: bool, e: str = "default") -> dict:
211
"""Function with positional and keyword-only parameters."""
212
return {"a": a, "b": b, "c": c, "d": d, "e": e}
213
214
# Preset keyword-only argument
215
simplified = partial(complex_func, d=True)
216
result = simplified(1, "hello", 2.5) # e uses default
217
print(result) # {"a": 1, "b": "hello", "c": 2.5, "d": True, "e": "default"}
218
219
# Preset positional and keyword arguments
220
more_simplified = partial(complex_func, 42, c=3.14, d=False, e="custom")
221
result2 = more_simplified("world")
222
print(result2) # {"a": 42, "b": "world", "c": 3.14, "d": False, "e": "custom"}
223
```
224
225
#### Error Handling
226
227
```python
228
from makefun import partial
229
230
def target_func(x: int, y: str) -> str:
231
return f"{x}: {y}"
232
233
# Invalid preset arguments
234
try:
235
invalid_partial = partial(target_func, 1, 2, 3) # Too many positional args
236
except ValueError as e:
237
print(f"Too many arguments: {e}")
238
239
try:
240
invalid_partial = partial(target_func, nonexistent_param="value") # Invalid keyword
241
except ValueError as e:
242
print(f"Invalid keyword: {e}")
243
```
244
245
### Comparison with functools.partial
246
247
Key advantages over `functools.partial`:
248
249
1. **Better Introspection**: Proper `inspect.signature()` support showing remaining parameters
250
2. **Enhanced Documentation**: Auto-generated docstrings showing preset values
251
3. **Signature Preservation**: Maintains proper type annotations and parameter information
252
4. **Better Error Messages**: Clear validation of preset arguments against function signature
253
254
```python
255
import functools
256
from makefun import partial
257
from inspect import signature
258
259
def example_func(x: int, y: str, z: float = 1.0) -> str:
260
"""Example function for comparison."""
261
return f"{x}, {y}, {z}"
262
263
# functools.partial
264
stdlib_partial = functools.partial(example_func, 42)
265
print(signature(stdlib_partial)) # (*args, **kwargs) - not helpful
266
267
# makefun.partial
268
enhanced_partial = partial(example_func, 42)
269
print(signature(enhanced_partial)) # (y: str, z: float = 1.0) -> str - precise!
270
271
print(stdlib_partial.__doc__) # partial(func, *args, **keywords) - generic
272
print(enhanced_partial.__doc__) # Shows actual preset values and original doc
273
```
274
275
### Integration with Other Makefun Features
276
277
Partial functions work seamlessly with other makefun utilities:
278
279
```python
280
from makefun import partial, wraps
281
282
def base_function(x: int, y: str, z: float = 1.0) -> str:
283
return f"Result: {x}, {y}, {z}"
284
285
# Create partial
286
preset_partial = partial(base_function, z=5.0)
287
288
# Wrap the partial with additional functionality
289
@wraps(preset_partial, prepend_args="verbose: bool = False")
290
def enhanced_partial(verbose, x, y):
291
if verbose:
292
print(f"Calling with x={x}, y={y}")
293
result = preset_partial(x, y)
294
if verbose:
295
print(f"Got: {result}")
296
return result
297
298
print(enhanced_partial(10, "test")) # Basic call
299
print(enhanced_partial(True, 20, "debug")) # Verbose call
300
```
301
302
### Function Attributes
303
304
Partial functions maintain compatibility with `functools.partial`:
305
306
```python
307
from makefun import partial
308
309
def original_func(a, b, c=1):
310
return a + b + c
311
312
# Create partial
313
p = partial(original_func, 10, c=5)
314
315
# Access original function
316
print(p.func) # Points to original_func
317
print(p.func(1, 2, 3)) # Can call original directly
318
319
# Check if it's a partial
320
import functools
321
print(isinstance(p, functools.partial)) # False - it's a generated function
322
print(hasattr(p, 'func')) # True - maintains .func attribute
323
```