Source code for mixinforge.mixins_and_metaclasses.immutable_mixin

"""Mixin for immutable objects with optimized hashing and equality.

Provides the ImmutableMixin class that enables value-based identity,
cached hashing, and optimized equality comparisons for objects that never
change after initialization. Subclasses define their identity through a
customizable key rather than through Python's id() function.
"""
from __future__ import annotations

from functools import cached_property
from typing import Any, Self

from .guarded_init_metaclass import GuardedInitMeta


[docs] class ImmutableMixin(metaclass=GuardedInitMeta): """Base mixin for objects that never change after initialization. Provides value-based identity semantics with optimized hashing and equality comparisons. Instead of using object identity (id), instances are compared based on a customizable identity key that represents their immutable state. This enables efficient use in sets and dictionaries while supporting value equality semantics. The mixin caches the hash value for O(1) lookups and uses hash-based short-circuiting in equality checks to avoid expensive comparisons. This is particularly beneficial for complex objects with many fields. Note that this mixin does not enforce immutability; subclasses are responsible for ensuring their instances truly never change after initialization. Subclasses must override get_identity_key() to return a hashable value that uniquely defines the object's identity based on its immutable state. """
[docs] def __init__(self, *args, **kwargs): """Initialize the mixin. """ super().__init__(*args, **kwargs)
[docs] def get_identity_key(self) -> Any: """Return a hashable value defining this object's identity. Subclasses must override this method to specify what makes an instance unique. The returned value is used for hashing and equality comparisons, enabling value-based semantics. Common implementations return a tuple of the object's immutable fields. The returned value must be hashable and must remain constant for the object's lifetime to maintain hash consistency. Returns: A hashable value uniquely identifying this object based on its immutable state. Raises: NotImplementedError: If not overridden by subclass. """ raise NotImplementedError( f"{type(self).__name__} must implement identity_key() method" )
@cached_property def identity_key(self) -> Any: """Cached identity key for consistent hashing and equality checks. Caches the result of get_identity_key() to ensure the same value is used throughout the object's lifetime. This guarantees hash stability and enables efficient repeated comparisons without recomputing the identity key. Returns: The cached identity key. Raises: RuntimeError: If called before initialization completes. """ if not self._init_finished: raise RuntimeError("Cannot get identity key of uninitialized object") return self.get_identity_key() def __hash__(self) -> int: """Return hash based on the cached identity key. Uses the cached identity key to compute the hash, ensuring O(1) performance for repeated hash operations. This enables efficient use in sets and dictionaries. Returns: Hash value derived from the identity key. Raises: RuntimeError: If initialization is incomplete. """ return hash(self.identity_key) def __eq__(self, other: Any) -> bool: """Check equality based on type and identity key. Implements optimized equality checking with multiple short-circuit paths: identity check, type check, hash comparison, and finally identity key comparison. The hash comparison provides fast rejection for unequal objects without comparing full identity keys. Args: other: Object to compare against. Returns: True if types and identity keys match, False otherwise, or NotImplemented for incompatible types. """ if self is other: return True elif type(self) is not type(other): return NotImplemented elif hash(self) != hash(other): return False else: return self.identity_key == other.identity_key def __ne__(self, other: Any) -> bool: """Check inequality based on type and identity key. Delegates to __eq__ and inverts the result, maintaining consistency with equality semantics and properly handling NotImplemented. Args: other: Object to compare against. Returns: True if objects are not equal, False otherwise, or NotImplemented for incompatible types. """ eq_result = self.__eq__(other) if eq_result is NotImplemented: return NotImplemented return not eq_result def __copy__(self) -> Self: """Return self since immutable objects need no copying. Immutable objects can safely share references instead of creating copies, improving memory efficiency and performance. """ return self def __deepcopy__(self, memo: dict[int, Any]) -> Self: """Return self since immutable objects need no deep copying. Immutable objects can safely share references instead of creating deep copies, improving memory efficiency and performance. """ return self