"""Function composition."""
from contextlib import contextmanager
from copy import deepcopy
from functools import partial
_modes = dict(args=False, kwargs=False, force_callable=False)
[docs]
def set_composition_mode(args=None, kwargs=None, force_callable=None):
"""Set composition mode.
Parameters:
args (bool, optional): whether to enable composition for positional arguments.
The default is ``None``, meaning that the current status will be kept.
kwargs (bool, optional): whether to enable composition for keyword arguments
The default is ``None``, meaning that the current status will be kept.
force_callable (bool, optional): whether to force return results to be callable
The default is ``None``, meaning that the current status will be kept.
Examples:
>>> set_composition_mode(args=False, kwargs=False, force_callable=False)
"""
if args is not None:
assert isinstance(args, bool)
_modes["args"] = args
if kwargs is not None:
assert isinstance(kwargs, bool)
_modes["kwargs"] = kwargs
if force_callable is not None:
assert isinstance(force_callable, bool)
_modes["force_callable"] = force_callable
[docs]
def disable_composition():
"""Disable ``args`` composition and ``kwargs`` composition.
Alias of ``set_composition_mode(args=False, kwargs=False)``
"""
set_composition_mode(args=False, kwargs=False)
[docs]
def enable_composition():
"""Enable args composition and kwargs composition.
Alias of ``set_composition_mode(args=True, kwargs=True)``
"""
set_composition_mode(args=True, kwargs=True)
[docs]
def disable_args_composition():
"""Disable ``args`` composition.
Alias of ``set_composition_mode(args=False)``
"""
set_composition_mode(args=False)
[docs]
def enable_args_composition():
"""Enable ``args`` composition.
Alias of ``set_composition_mode(args=True)``
"""
set_composition_mode(args=True)
[docs]
def disable_kwargs_composition():
"""Disable ``kwargs`` composition.
Alias of ``set_composition_mode(kwargs=False)``
"""
set_composition_mode(kwargs=False)
[docs]
def enable_kwargs_composition():
"""Enable ``kwargs`` composition.
Alias of ``set_composition_mode(kwargs=True)``
"""
set_composition_mode(kwargs=True)
[docs]
def disable_force_callable():
"""Disable force callable.
Alias of ``set_composition_mode(force_callable=False)``
"""
set_composition_mode(force_callable=False)
[docs]
def enable_force_callable():
"""Enable force callable.
Alias of ``set_composition_mode(force_callable=True)``
"""
set_composition_mode(force_callable=True)
[docs]
def args_composition_enabled():
"""Return ``True`` when ``args`` composition is enabled.
Returns:
bool:
"""
return _modes["args"]
[docs]
def kwargs_composition_enabled():
"""Return ``True`` when ``kwargs`` composition is enabled.
Returns:
bool:
"""
return _modes["kwargs"]
def force_callable_enabled():
"""Return ``True`` when force callable is enabled.
Returns:
bool:
"""
return _modes["force_callable"]
[docs]
def composition_enabled():
"""Return ``True`` when ``args`` composition or ``kwargs`` composition or force callable is enabled.
Returns:
bool:
"""
return _modes["args"] or _modes["kwargs"] or _modes["force_callable"]
[docs]
@contextmanager
def composition_mode_context(args=None, kwargs=None, force_callable=None):
"""Context manager to temporarily set composite mode in a ``with`` statement.
Parameters:
args (bool): whether to enable composition for positional arguments
kwargs (bool): whether to enable composition for keyword arguments
force_callable (bool): whether to force return results to be callable
Examples:
>>> force_callable_enabled()
False
>>> with composition_mode_context(args=False, kwargs=False, force_callable=True):
... force_callable_enabled()
True
>>> force_callable_enabled()
False
"""
saved_modes = deepcopy(_modes)
try:
if args is not None:
_modes["args"] = args
if kwargs is not None:
_modes["kwargs"] = kwargs
if force_callable is not None:
_modes["force_callable"] = force_callable
yield
finally:
for key in ["args", "kwargs", "force_callable"]:
_modes[key] = saved_modes[key]
def callable_arguments(*args, **kwargs):
"""Check whether the argument will lead to a callable result.
Parameters:
*args: values or callable objects.
**kwargs: keyword arguments, either values or callable objects.
Returns:
bool
Examples:
>>> callable_arguments(1, 2, 3)
False
>>> enable_composition()
>>> callable_arguments(sum)
True
>>> disable_composition()
"""
if force_callable_enabled():
return True
if args_composition_enabled():
if any(callable(arg) for arg in args):
return True
if kwargs_composition_enabled():
for key in kwargs:
kwarg = kwargs[key]
if callable(kwarg):
return True
return False
def composite_callable(how, /, *args, **kwargs):
"""Combine multiple callables into a single callable, using a combining function.
Parameters:
how : Callable object that combines multiple results.
*args : Callable objects or values.
**kwargs : Keyword arguments to pass to the callables.
Returns:
A callable object.
Examples:
>>> from calcpy import itemgetter
>>> enable_composition()
>>> composite_callable(max, itemgetter(1), 7, itemgetter(2, default=3))([3, 4, 5])
7
>>> composite_callable(min, itemgetter(1), 7, itemgetter(2, default=3))([3, 4, 5])
4
>>> composite_callable(max, 3, 4, 5)()
5
>>> disable_composition()
"""
def f(*args_, **kwargs_):
_args = []
for arg in args:
if args_composition_enabled() and callable(arg):
_arg = arg(*args_, **kwargs_)
else:
_arg = arg
_args.append(_arg)
_kwargs = {}
for key in kwargs:
kwarg = kwargs[key]
if kwargs_composition_enabled() and callable(kwarg):
_kwarg = kwarg(*args_, **kwargs_)
else:
_kwarg = kwarg
_kwargs[key] = _kwarg
result = how(*_args, **_kwargs)
return result
return f
def composite(how, /, *args, **kwargs):
"""Combine multiple callables into a single callable, using a combining function.
Parameters:
how : Callable object that combines multiple results.
*args : Callable objects or values.
**kwargs : Keyword arguments to pass to the callables.
Returns:
callable:
Examples:
>>> from calcpy import itemgetter
>>> enable_composition()
>>> composite(max, itemgetter(1), 7, itemgetter(2, default=3))([3, 4, 5])
7
>>> composite(min, itemgetter(1), 7, itemgetter(2, default=3))([3, 4, 5])
4
>>> composite(max, 3, 4, 5)
5
>>> disable_composition()
"""
if callable_arguments(*args, **kwargs):
return composite_callable(how, *args, **kwargs)
return how(*args, **kwargs)
[docs]
def componentize(how, /):
"""Decorate a callable function so that it becomes a building block of function composition.
Need to enable composition mode before using this function.
Parameters:
how : Callable object that combines multiple results.
Returns:
callable:
Examples:
>>> from calcpy import itemgetter
>>> enable_composition()
>>> max_ = componentize(max)
>>> max_(itemgetter(1), 7, itemgetter(2, default=3))([3, 4, 5])
7
>>> min_ = componentize(min)
>>> min_(itemgetter(1), 7, itemgetter(2, default=3))([3, 4, 5])
4
>>> min_(3, 4, 5)
3
>>> disable_composition()
"""
if not composition_enabled():
return how # avoid mess up function signature and docstring
return partial(composite, how)
def getcomponent(module, name):
f = getattr(module, name)
component = componentize(f)
return component