Source code for sidekick.functions.core_functions

import inspect
from functools import singledispatch

from ..typing import Any, Callable, FunctionType, Mapping, FunctionTypes
from ..typing import NamedTuple, Iterable, Tuple, TYPE_CHECKING

if TYPE_CHECKING:
    from .fn import fn  # noqa: F401
    from .. import api as sk  # noqa: F401

_new = object.__new__


[docs]def to_fn(func: Any) -> "fn": """ Convert callable to an :class:`fn` object. If func is already an :class:`fn` instance, it is passed as is. """ if isinstance(func, fn): return func else: return fn(func)
[docs]def to_function(func: Any, name=None) -> FunctionType: """ Return object as as Python function. Non-functions are wrapped into a function definition. """ func = to_callable(func) if isinstance(func, FunctionType): if name is not None and func.__name__ == "<lambda>": func.__name__ = name return func def f(*args, **kwargs): return func(*args, **kwargs) if name is not None: f.__name__ = name else: name = getattr(type(func), "__name__", "function") f.__name__ = getattr(func, "__name__", name) return f
@singledispatch def _to_callable(func: Any): try: return func.__sk_callable__ except AttributeError: pass if callable(func): return func else: raise TypeError("cannot be interpreted as a callable: %r" % func)
[docs]def to_callable(func: Any) -> Callable: """ Convert argument to callable. This differs from to_function in which it returns the most efficient version of object that has the same callable interface as the argument. This *removes* sidekick's function wrappers such as fn and try to convert argument to a straightforward function value. This defines the following semantics: * Sidekick's fn: extract the inner function. * None: return the identity function. * Mappings: map.__getitem__ * Functions, methods and other callables: returned as-is. """ try: return func.__sk_callable__ except AttributeError: if isinstance(func, FunctionTypes): return func return _to_callable(func)
to_callable.register = _to_callable.register to_callable.dispatch = _to_callable.dispatch to_callable.register(FunctionType, lambda fn: fn) to_callable.register(type(None), lambda fn: lambda x: x) to_callable.register(Mapping, lambda dic: dic.__getitem__)
[docs]@singledispatch def arity(func: Callable): """ Return arity of a function. Examples: >>> from operator import add >>> sk.arity(add) 2 """ if hasattr(func, "arity"): return func.arity() spec = inspect.getfullargspec(func) if spec.varargs or spec.varkw or spec.kwonlyargs: raise TypeError("cannot curry a variadic function") return len(spec.args)
[docs]@singledispatch def signature(func: Callable) -> inspect.Signature: """ Return the signature of a function. """ if hasattr(func, "signature"): return func.signature() return inspect.Signature.from_callable(func)
[docs]@singledispatch def stub(func: Callable) -> "Stub": """ Return a :class:`Stub` object representing the function signature. """ sig = signature(func) return Stub(func.__name__, (sig,))
[docs]class Stub(NamedTuple): """ Represent a function declaration Stub. """ name: str signatures: Tuple[inspect.Signature] def __str__(self): return self.render() def _stub_declarations(self) -> Iterable[str]: name = self.name if len(self.signatures) == 0: yield f"def {name}(*args, **kwargs) -> Any: ..." elif len(self.signatures) == 1: yield f"def {name}{self.signatures[0]}: ..." else: for sep, sig in enumerate(self.signatures): prefix = "\n" if sep else "" return f"{prefix}@overload\ndef {name}{sig}: ..." def _import_declarations(self) -> Iterable[str]: raise NotImplementedError
[docs] def required_imports(self) -> set: """ Return set of imported symbols """ return set()
[docs] def render(self, imports=False) -> str: """ Render complete stub file, optionally including imports. """ stubs = "\n".join(self._stub_declarations()) if imports: head = "\n".join(self._import_declarations()) return f"{head}\n\n{stubs}" else: return stubs
[docs]def quick_fn(func: callable) -> "fn": """ Faster fn constructor. This is about twice as fast as the regular fn() constructor. It assumes that fn is """ new: fn = _new(fn) new._func = func new.__dict__ = {} return new
def make_xor(f, g): """ Compose functions in a short-circuit version of xor using the following table: +--------+--------+-------+ | A | B | A^B | +--------+--------+-------+ | truthy | truthy | not B | +--------+--------+-------+ | truthy | falsy | A | +--------+--------+-------+ | falsy | truthy | B | +--------+--------+-------+ | falsy | falsy | B | +--------+--------+-------+ """ def xor(*args, **kwargs): a = f(*args, **kwargs) if a: b = g(*args, **kwargs) return not b if b else a else: return g(*args, **kwargs) return xor from .fn import fn # noqa: E402, F811