Small library to dynamically create python functions.
—
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.
from typing import Callable, Any, TypeVar
T = TypeVar('T')Improved version of functools.partial with better introspection and automatically generated documentation.
def partial(f: Callable[..., T], *preset_pos_args: Any, **preset_kwargs: Any) -> Callable[..., T]:
"""
Enhanced functools.partial with better introspection and documentation.
Parameters:
- f: Callable[..., T], function to partially apply
- *preset_pos_args: Any, positional arguments to preset/bind
- **preset_kwargs: Any, keyword arguments to preset/bind
Returns:
Callable[..., T]: Partial function with enhanced metadata and signature
The returned function has:
- Proper signature reflecting remaining parameters
- Auto-generated docstring showing preset values
- .func attribute pointing to original function (like functools.partial)
- Better introspection support with inspect.signature()
Raises:
ValueError: If preset arguments don't match function signature
"""Decorator version of partial function for cleaner syntax when creating partial functions.
def with_partial(*preset_pos_args: Any, **preset_kwargs: Any) -> Callable[[Callable[..., T]], Callable[..., T]]:
"""
Decorator to create partial function with preset arguments.
Parameters:
- *preset_pos_args: Any, positional arguments to preset
- **preset_kwargs: Any, keyword arguments to preset
Returns:
Callable[[Callable[..., T]], Callable[..., T]]: Decorator that creates partial function
"""from makefun import partial
from inspect import signature
def multiply(x: int, y: int, z: int = 1) -> int:
"""Multiply three numbers together."""
return x * y * z
# Create partial with first argument preset
double = partial(multiply, 2)
print(signature(double)) # (y: int, z: int = 1) -> int
print(double(5)) # 10 (2 * 5 * 1)
print(double(3, 4)) # 24 (2 * 3 * 4)
# Create partial with keyword argument preset
multiply_by_10 = partial(multiply, z=10)
print(signature(multiply_by_10)) # (x: int, y: int) -> int
print(multiply_by_10(2, 3)) # 60 (2 * 3 * 10)from makefun import partial
def api_request(method: str, url: str, headers: dict = None, timeout: int = 30) -> dict:
"""Make an HTTP API request."""
return {
"method": method,
"url": url,
"headers": headers or {},
"timeout": timeout
}
# Create partial for GET requests
get_request = partial(api_request, "GET", timeout=60)
# Automatic documentation generation
print(get_request.__doc__)
# <This function is equivalent to 'api_request('GET', url, headers=None, timeout=60)', see original 'api_request' doc below.>
# Make an HTTP API request.
print(get_request.__name__) # "api_request" (preserved from original)from makefun import partial
def format_message(template: str, name: str, age: int, city: str = "Unknown") -> str:
"""Format a message with user information."""
return template.format(name=name, age=age, city=city)
# Preset template and city
welcome_message = partial(format_message, "Welcome {name}! You are {age} years old and live in {city}.", city="New York")
print(welcome_message("Alice", 25)) # Welcome Alice! You are 25 years old and live in New York.
print(welcome_message("Bob", 30, "Chicago")) # Welcome Bob! You are 30 years old and live in Chicago.from makefun import with_partial
# Define base function
def compute_score(base: int, multiplier: float, bonus: int = 0) -> float:
"""Compute final score with base, multiplier, and bonus."""
return (base + bonus) * multiplier
# Create specialized versions using decorator
@with_partial(multiplier=1.5)
def boosted_score(base: int, bonus: int = 0) -> float:
pass # Implementation replaced by partial
@with_partial(multiplier=2.0, bonus=10)
def premium_score(base: int) -> float:
pass # Implementation replaced by partial
print(boosted_score(100)) # 150.0 (100 * 1.5)
print(boosted_score(100, 20)) # 180.0 ((100 + 20) * 1.5)
print(premium_score(100)) # 220.0 ((100 + 10) * 2.0)from makefun import partial
from typing import List, Dict, Optional
def analyze_data(data: List[Dict], method: str, normalize: bool = True,
weights: Optional[Dict] = None, *extra_params, **options) -> Dict:
"""Analyze data using specified method with various options."""
result = {
"method": method,
"data_count": len(data),
"normalized": normalize,
"weights": weights,
"extra_params": extra_params,
"options": options
}
return result
# Create partial with method and normalization preset
statistical_analysis = partial(analyze_data, method="statistical", normalize=True)
# Remaining signature handles complex parameters correctly
sample_data = [{"value": 1}, {"value": 2}]
result = statistical_analysis(sample_data, weights={"a": 0.5}, "param1", "param2",
threshold=0.1, debug=True)
print(result)from makefun import partial
import asyncio
def number_generator(start: int, end: int, step: int = 1, prefix: str = "Num"):
"""Generate numbers with optional prefix."""
for i in range(start, end, step):
yield f"{prefix}: {i}"
# Create partial generator
even_numbers = partial(number_generator, step=2, prefix="Even")
for num in even_numbers(0, 10):
print(num) # Even: 0, Even: 2, Even: 4, etc.
# Async function partial
async def fetch_data(url: str, method: str = "GET", timeout: int = 30) -> dict:
"""Simulate async data fetching."""
await asyncio.sleep(0.1)
return {"url": url, "method": method, "timeout": timeout}
# Create partial async function
quick_fetch = partial(fetch_data, timeout=5)
result = asyncio.run(quick_fetch("https://api.example.com", "POST"))
print(result)from makefun import partial
def complex_func(a: int, b: str, c: float = 1.0, *, d: bool, e: str = "default") -> dict:
"""Function with positional and keyword-only parameters."""
return {"a": a, "b": b, "c": c, "d": d, "e": e}
# Preset keyword-only argument
simplified = partial(complex_func, d=True)
result = simplified(1, "hello", 2.5) # e uses default
print(result) # {"a": 1, "b": "hello", "c": 2.5, "d": True, "e": "default"}
# Preset positional and keyword arguments
more_simplified = partial(complex_func, 42, c=3.14, d=False, e="custom")
result2 = more_simplified("world")
print(result2) # {"a": 42, "b": "world", "c": 3.14, "d": False, "e": "custom"}from makefun import partial
def target_func(x: int, y: str) -> str:
return f"{x}: {y}"
# Invalid preset arguments
try:
invalid_partial = partial(target_func, 1, 2, 3) # Too many positional args
except ValueError as e:
print(f"Too many arguments: {e}")
try:
invalid_partial = partial(target_func, nonexistent_param="value") # Invalid keyword
except ValueError as e:
print(f"Invalid keyword: {e}")Key advantages over functools.partial:
inspect.signature() support showing remaining parametersimport functools
from makefun import partial
from inspect import signature
def example_func(x: int, y: str, z: float = 1.0) -> str:
"""Example function for comparison."""
return f"{x}, {y}, {z}"
# functools.partial
stdlib_partial = functools.partial(example_func, 42)
print(signature(stdlib_partial)) # (*args, **kwargs) - not helpful
# makefun.partial
enhanced_partial = partial(example_func, 42)
print(signature(enhanced_partial)) # (y: str, z: float = 1.0) -> str - precise!
print(stdlib_partial.__doc__) # partial(func, *args, **keywords) - generic
print(enhanced_partial.__doc__) # Shows actual preset values and original docPartial functions work seamlessly with other makefun utilities:
from makefun import partial, wraps
def base_function(x: int, y: str, z: float = 1.0) -> str:
return f"Result: {x}, {y}, {z}"
# Create partial
preset_partial = partial(base_function, z=5.0)
# Wrap the partial with additional functionality
@wraps(preset_partial, prepend_args="verbose: bool = False")
def enhanced_partial(verbose, x, y):
if verbose:
print(f"Calling with x={x}, y={y}")
result = preset_partial(x, y)
if verbose:
print(f"Got: {result}")
return result
print(enhanced_partial(10, "test")) # Basic call
print(enhanced_partial(True, 20, "debug")) # Verbose callPartial functions maintain compatibility with functools.partial:
from makefun import partial
def original_func(a, b, c=1):
return a + b + c
# Create partial
p = partial(original_func, 10, c=5)
# Access original function
print(p.func) # Points to original_func
print(p.func(1, 2, 3)) # Can call original directly
# Check if it's a partial
import functools
print(isinstance(p, functools.partial)) # False - it's a generated function
print(hasattr(p, 'func')) # True - maintains .func attributeInstall with Tessl CLI
npx tessl i tessl/pypi-makefun