Source code for mixinforge.mixins_and_metaclasses.parameterizable_mixin

"""Basic infrastructure for parameterizable classes.

This module provides functionality for working with parameterizable classes:
classes that have (hyper)parameters which define an object's
configuration/identity, but not its internal contents or data.
Such parameters are typically passed to the __init__ method.

The module provides an API for getting parameter values from an object,
and for converting the parameters to and from a portable dictionary
(a dictionary with sorted str keys that only contains
basic types and portable sub-dictionaries).
"""

import inspect
from copy import deepcopy
from typing import Any, Self

from ..utility_functions.dict_sorter import sort_dict_by_keys
from ..utility_functions.json_processor import dumpjs, JsonSerializedObject


[docs] class ParameterizableMixin: """Base class for parameterizable classes. Classes deriving from this base expose a stable set of configuration parameters that define their behavior and identity. Subclasses implement get_params to return these parameters, which can then be serialized to and from a portable JSON representation. Note: This class is intended to be subclassed. The default implementation of get_params returns an empty mapping. """ def __repr__(self) -> str: return f"{type(self).__name__}({self.get_params()})"
[docs] def get_params(self) -> dict[str, Any]: """Return this instance's configuration parameters. Parameters define the object's configuration but not its internal contents or data. They are typically passed to __init__ at creation time. Returns: A mapping of parameter names to values. Note: Subclasses should override this method to return their specific parameters. The default implementation returns an empty dictionary. """ params = dict() return params
[docs] def clone(self, **kwargs: Any) -> Self: """Create a new instance with the same parameters, optionally overriding some. Args: **kwargs: Parameter overrides to apply to the clone. Returns: A new instance with parameters from this instance, updated with kwargs. """ params = deepcopy(self.get_params()) params.update(deepcopy(kwargs)) return type(self)(**params)
def _extend_parent_params(self, **new_params: Any) -> dict[str, Any]: """Extend parent parameters with keyword overrides.""" parent = super(type(self), self) params = parent.get_params() if hasattr(parent, "get_params") else {} params = {**params, **new_params} params = sort_dict_by_keys(params) return params
[docs] def get_jsparams(self) -> JsonSerializedObject: """Return this instance's parameters encoded as JSON. Returns: JSON string produced by dumpjs. """ return dumpjs(sort_dict_by_keys(self.get_params()))
[docs] @classmethod def get_default_params(cls) -> dict[str, Any]: """Get the default parameters of the class as a dictionary. Default values are taken from keyword parameters of __init__ and returned as a key-sorted dictionary. Subclasses may override if default computation requires custom logic. Returns: The class's default parameters sorted by key. """ signature = inspect.signature(cls.__init__) # Skip the first parameter (self/cls) params_to_consider = list(signature.parameters.values())[1:] params = { p.name: p.default for p in params_to_consider if p.default is not inspect.Parameter.empty } sorted_params = sort_dict_by_keys(params) return sorted_params
[docs] @classmethod def get_default_jsparams(cls) -> JsonSerializedObject: """Return default constructor parameters encoded as JSON. Returns: JSON string with default parameters. """ return dumpjs(cls.get_default_params())
@property def essential_param_names(self) -> set[str]: """Names of parameters that define the object's core identity and behavior. Essential parameters are those that fundamentally define an object's behavior or identity - for example, the maximum number of trees in a random forest or the maximum depth of a decision tree. These parameters are oftentimes immutable throughout the object's lifetime. They are guaranteed to be preserved during the copying/deepcopying, serialization/deserialization processes, and similar operations. Note: Subclasses should override this property to specify which parameters are essential. The default implementation considers all parameters essential. Returns: Names of essential parameters. """ return set(self.get_params().keys()) @property def auxiliary_param_names(self) -> set[str]: """Names of the object's auxiliary parameters. Auxiliary parameters are parameters that do not fundamentally impact the object's behavior or identity. These parameters might include settings like logging verbosity, debug flags, or probability thresholds for consistency checks. They are considered "disregardable" in the sense that they are not guaranteed to be preserved during serialization/deserialization processes, or even during simple copying/deepcopying operations. Returns: Set of auxiliary parameter names. """ return set(self.get_params().keys()) - self.essential_param_names
[docs] def get_essential_params(self) -> dict[str, Any]: """Return only the essential parameters. Returns: Mapping of essential parameter names to values. """ return {k: v for k, v in self.get_params().items() if k in self.essential_param_names}
[docs] def get_essential_jsparams(self) -> JsonSerializedObject: """Return essential parameters encoded as JSON. Returns: JSON string with essential parameters. """ return dumpjs(self.get_essential_params())
[docs] def get_auxiliary_params(self) -> dict[str, Any]: """Return only the auxiliary parameters. Returns: Mapping of auxiliary parameter names to values. """ return {k: v for k, v in self.get_params().items() if k in self.auxiliary_param_names}
[docs] def get_auxiliary_jsparams(self) -> JsonSerializedObject: """Return auxiliary parameters encoded as JSON. Returns: JSON string with auxiliary parameters. """ return dumpjs(self.get_auxiliary_params())