Source code for mixle.utils.aliasing

"""Backward-compatible keyword-argument aliasing for distribution and estimator constructors.

Some distribution constructors keep legacy positional names while also accepting descriptive public
keyword aliases. To keep existing code working while preferred spellings become canonical,
constructors accept both the legacy name and the preferred name and reconcile them with
:func:`coalesce_alias`.

Usage::

    def __init__(self, components, w=None, name=None, *, weights=None):
        w = coalesce_alias('w', w, 'weights', weights)

The preferred (alias) argument is keyword-only so it never shadows a positional argument, and the
legacy argument keeps its position. Passing both raises ``TypeError``; passing neither raises
``TypeError`` for required arguments.
"""

import numbers
from typing import Any

__all__ = ["coalesce_alias", "require", "broadcast_pseudo_count", "MISSING"]


class _Missing:
    """Sentinel type for "argument not supplied" so that ``None`` stays a valid explicit value."""

    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __repr__(self):
        return "<MISSING>"


MISSING = _Missing()


[docs] def require(name: str, value: Any, *, default: Any = MISSING) -> Any: """Return ``value`` unless it is the not-supplied sentinel, in which case raise ``TypeError``. Used for required positional arguments that were given a sentinel default so an aliased earlier argument could become optional (which would otherwise force a non-default argument to follow a defaulted one). """ if value is default: raise TypeError("missing required argument %r" % name) return value
[docs] def broadcast_pseudo_count(pseudo_count: Any, arity: int) -> Any: """Broadcast a scalar ``pseudo_count`` to the tuple arity a composite estimator stores. The ``dist.estimator(pseudo_count=...)`` facades are scalar everywhere, while some estimator constructors keep a historical per-statistic tuple. Those constructors accept the scalar spelling too by broadcasting it to every slot -- exactly what the facades do (``(pc, pc)``). Tuples/lists and ``None`` pass through unchanged. Args: pseudo_count (Any): Scalar smoothing count, per-statistic tuple, or ``None``. arity (int): Number of tuple slots the estimator stores. Returns: ``(pseudo_count,) * arity`` for a real scalar, else ``pseudo_count`` unchanged. """ if isinstance(pseudo_count, numbers.Real) and not isinstance(pseudo_count, bool): return (pseudo_count,) * arity return pseudo_count
[docs] def coalesce_alias( canonical_name: str, canonical_value: Any, alias_name: str, alias_value: Any, *, required: bool = True, default: Any = None, ) -> Any: """Reconcile a canonical (legacy) argument with its preferred alias. Args: canonical_name (str): Name of the legacy argument, used in error messages. canonical_value (Any): Value bound to the legacy argument. alias_name (str): Name of the preferred argument, used in error messages. alias_value (Any): Value bound to the preferred argument. required (bool): If True, raise when neither argument was supplied. default (Any): Sentinel marking "not supplied" for both arguments. Compared by identity, so the legacy argument's declared default must match this value. Returns: The supplied value, preferring the alias when both resolve to non-default (which is only reachable when exactly one was supplied). Raises: TypeError: If both arguments are supplied, or if neither is supplied and ``required``. """ alias_given = alias_value is not default canonical_given = canonical_value is not default if alias_given and canonical_given: raise TypeError("%r and its alias %r are mutually exclusive; pass only one" % (canonical_name, alias_name)) if alias_given: return alias_value if canonical_given: return canonical_value if required: raise TypeError("missing required argument %r (or its alias %r)" % (canonical_name, alias_name)) return default