Source code for utilipy.data_utils.decorators
# -*- coding: utf-8 -*-
"""`data_util` decorators."""
##############################################################################
# IMPORTS
# BUILT-IN
import sys
import typing as T
# THIRD PARTY
import numpy as np
from typing_extensions import Literal
# PROJECT-SPECIFIC
from utilipy.utils import functools
##############################################################################
# PARAMETERS
# Windows prints numpy differently. Numpy outputs need to be ignored.
# TODO: a better platform-specific fix for numpy.
if sys.platform.startswith("win"):
__doctest_skip__ = ["idxDecorator"]
##############################################################################
# CODE
##############################################################################
[docs]def idxDecorator(
function: T.Optional[T.Callable] = None,
*,
as_ind: T.Union[bool, Literal["flatten"]] = False,
_doc_fmt: T.Optional[dict] = None, # ibid
_doc_style: T.Union[str, T.Callable, None] = None, # ibid
) -> T.Callable:
"""Control whether to return boolean array or indices.
for functions which return bool arrays
adds `as_ind` as a kwarg to decorated function
Parameters
----------
function : Callable, optional
(default None)
the function to be decoratored
if None, then returns decorator to apply.
as_ind : bool or "flatten", optional
(default False)
whether to return bool array or indices
(``where(bool array == np.True_)``)
if "flatten", flattens a nested list with only 1 element
ie ([0], ) -> [0]
sets the default behavior for the wrapped `function`
Returns
-------
wrapper : Callable
wrapper for `function`
includes the original function in a method ``.__wrapped__``
Other Parameters
----------------
_doc_fmt : dict, optional
docstring formatter
argument into :func:`~utilipy.utils.functools.wraps`
_doc_style : str or Callable, optional
docstring style
argument into :func:`~utilipy.utils.functools.wraps`
Notes
-----
Adds `as_ind` and other parameters to the function signature and docstring.
Examples
--------
Use the Standard Decorator:
>>> x = np.array([0, 2])
>>> @idxDecorator
... def func1(x):
... return x < 1
calling normally
>>> func1(x) # doctest: +SKIP
array([ True, False], dtype=bool))
using added kwarg
>>> func1(x, as_ind=True)
(array([0]),)
and flattening
>>> func1(x, as_ind="flatten")
array([0])
Set a Different Default:
>>> @idxDecorator(as_ind=True)
... def func2(x):
... return x < 1
>>> func2(x)
(array([0]),)
>>> func2(x, as_ind=False) # doctest: +SKIP
array([ True, False])
Making a New Decorator:
>>> trueidxdec = idxDecorator(as_ind="flatten")
>>> @trueidxdec
... def func3(x):
... return x < 1
>>> func3(x)
array([0])
>>> func3(x, as_ind=False) # doctest: +SKIP
array([ True, False])
Wrapping Existing Functions
>>> def func(x):
... return x < 1
>>> newfunc = idxDecorator(func, as_ind=True)
>>> newfunc(x)
(array([0]),)
>>> newfunc(x, as_ind=False) # doctest: +SKIP
array([ True, False])
"""
if function is None: # allowing for optional arguments
return functools.partial(
idxDecorator,
as_ind=as_ind,
_doc_fmt=_doc_fmt,
_doc_style=_doc_style,
)
@functools.wraps(function, _doc_fmt=_doc_fmt, _doc_style=_doc_style)
def wrapper(
*args: T.Any, as_ind: bool = as_ind, **kwargs: T.Any
) -> T.Sequence:
"""Index-decorator wrapper docstring, overwritten by `function`.
Other Parameters
----------------
as_ind : bool or "flatten", optional
(default {as_ind})
whether to return a boolean array, or array of indices.
Raises
------
`~ValueError`
if `as_ind` is "flatten" and cannot be flattened (len > 1)
"""
# Step 1: call function
bool_arr = function(*args, **kwargs)
# Step 2: determine whether to return indices or bool array
if as_ind: # works for bool or full str
# get the indices
inds = np.nonzero(np.asarray(bool_arr) == np.True_)
# determine whether to return as-is, or flatten
if as_ind == "flatten":
if len(inds) == 1: # nested list with only 1 element
return inds[0]
else: # not a valid option
raise ValueError
else: # do not flatten
return inds
# return a bool array
return bool_arr
# /def
return wrapper
# /def
##############################################################################
# END