Source code for calcpy._fun

from functools import wraps
from inspect import signature, Parameter


[docs] def curry(*args, **kwargs): """Fill arguments of a callable. If you want to fill positional arguments in the middle without filling argumetns in the begining, you can use ``prioritize()`` to move those positional parameter to the beginning, and then fill them using this ``curry()``. Parameters: args (tuple): Positional arguments to fill. kwargs (dict): Keyword arguments to fill. Returns: Callable[callable, callable]: Examples: Use as a decorator: >>> @curry(2, 3) ... def muladd(a, b, c): ... return a * b + c >>> muladd(4) 10 Use as a decorator, together with ``prioritize()``: >>> from calcpy.fun import prioritize >>> @curry(2, 3) ... @prioritize(-2, -1) ... def muladd(a, b, c): ... return a * b + c >>> muladd(4) 14 """ def wrapper(f): @wraps(f) def fun(*arguments, **keywordarguments): return f(*args, *arguments, **kwargs, **keywordarguments) return fun return wrapper
[docs] def extargs(mode=None): """Enhance a function so that it can accept unused parameters. Parameters: mode (optional): Method to resolve when both positional argument and keyword argument tries to write to the same parameter. The default value ``None`` means to raise when there are conflicts. ``inspect.Parameter.POSITIONAL_ONLY`` means to use the values in positional arguments. ``inspect.Parameter.KEYWORD_ONLY`` means to use the values in keyword arguments. Returns: callable: Decorator that enhances the target function with parameter filtering. Examples: Enrich a function so that it can accept additional parameters, overwriting default values. >>> def print_fixed(a, /, b, c="c", *, d, e="e"): # fixed parameters ... print(f"a={a}, b={b}, c={c}, d={d}, e={e}") >>> eprint_fixed = extargs()(print_fixed) >>> eprint_fixed(0, 1, 2, 3, d="D", e="E", f="F") a=0, b=1, c=2, d=D, e=E Use default values. >>> eprint_fixed(0, 1, d="D") a=0, b=1, c=c, d=D, e=e Raise due to missing obligatory positional arguments. >>> eprint_fixed(c="C", d="D", e="E", f="F") Traceback (most recent call last): ... TypeError: missing a required argument: 'a' Raise due to missing obligatory keyword arguments. >>> eprint_fixed(0, 1) Traceback (most recent call last): ... TypeError: missing a required argument: 'd' Raise due to the conflict between positional arguments and keyword arguments. >>> eprint_fixed(0, 1, 2, 3, c="C", d="D", e="E", f="F") Traceback (most recent call last): ... TypeError: multiple values for argument 'c' Use positional argument values when conflicting. >>> import inspect >>> pprint_fixed = extargs(inspect.Parameter.POSITIONAL_ONLY)(print_fixed) >>> pprint_fixed(0, 1, 2, 3, c="C", d="D", e="E", f="F") a=0, b=1, c=2, d=D, e=E Use keyword argument values when conflicting. >>> kprint_fixed = extargs(inspect.Parameter.KEYWORD_ONLY)(print_fixed) >>> kprint_fixed(0, 1, 2, 3, c="C", d="D", e="E", f="F") a=0, b=1, c=C, d=D, e=E Deal with varied arguments. >>> def print_var(a, /, b, c="c", *args, d, e="e", **kwargs): # var parameters ... print(f"a={a}, b={b}, c={c}, args={args}, d={d}, e={e}, kwargs={kwargs}") >>> eprint_var = extargs()(print_var) >>> eprint_var(0, 1, 2, 3, c="C", d="D", e="E", f="F") Traceback (most recent call last): ... TypeError: multiple values for argument 'c' >>> pprint_var = extargs(inspect.Parameter.POSITIONAL_ONLY)(print_var) >>> pprint_var(0, 1, 2, 3, c="C", d="D", e="E", f="F") a=0, b=1, c=2, args=(3,), d=D, e=E, kwargs={'f': 'F'} >>> kprint_var = extargs(inspect.Parameter.KEYWORD_ONLY)(print_var) >>> kprint_var(0, 1, 2, 3, c="C", d="D", e="E", f="F") a=0, b=1, c=C, args=(3,), d=D, e=E, kwargs={'f': 'F'} Raise due to missing obligatory keyword arguments, when the original function has varied arguments. >>> eprint_var(d="D") Traceback (most recent call last): ... TypeError: missing a required argument: 'a' """ def decorator(f): sig = signature(f) params = sig.parameters # Identify parameter characteristics has_var_args = any(param.kind == param.VAR_POSITIONAL for param in params.values()) has_var_kwargs = any(param.kind == param.VAR_KEYWORD for param in params.values()) max_positional = sum( 1 for param in params.values() if param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD) ) @wraps(f) def wrapper(*args, **kwargs): # Process positional arguments if not has_var_args: args = args[:max_positional] # Filter keyword arguments if not has_var_kwargs: kwargs = { k: v for k, v in kwargs.items() if k in params and params[k].kind in (Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD) } # Conflict resolution if mode == Parameter.KEYWORD_ONLY: # Update positional argument values new_args = [] for i, param in enumerate(params.values()): if i < len(args): if param.name in kwargs: new_args.append(kwargs[param.name]) else: new_args.append(args[i]) args = tuple(new_args) if mode is not None: # inspect.Parameter.POSITIONAL_ONLY or KEYWORD_ONLY # Remove conflicting keys position_assigned = set() for i, param in enumerate(params.values()): if i < len(args) and param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD): position_assigned.add(param.name) kwargs = { k: v for k, v in kwargs.items() if k not in position_assigned } # Bind and execute bound_args = sig.bind(*args, **kwargs) bound_args.apply_defaults() fun = f(*bound_args.args, **bound_args.kwargs) return fun return wrapper return decorator