docs for muutils v0.6.17
View Source on GitHub

# muutils.misc.freezing

View Source
View on GitHub

      1from __future__ import annotations
      2
      3
      4class FrozenDict(dict):
      5    def __setitem__(self, key, value):
      6        raise AttributeError("dict is frozen")
      7
      8    def __delitem__(self, key):
      9        raise AttributeError("dict is frozen")
     10
     11
     12class FrozenList(list):
     13    def __setitem__(self, index, value):
     14        raise AttributeError("list is frozen")
     15
     16    def __delitem__(self, index):
     17        raise AttributeError("list is frozen")
     18
     19    def append(self, value):
     20        raise AttributeError("list is frozen")
     21
     22    def extend(self, iterable):
     23        raise AttributeError("list is frozen")
     24
     25    def insert(self, index, value):
     26        raise AttributeError("list is frozen")
     27
     28    def remove(self, value):
     29        raise AttributeError("list is frozen")
     30
     31    def pop(self, index=-1):
     32        raise AttributeError("list is frozen")
     33
     34    def clear(self):
     35        raise AttributeError("list is frozen")
     36
     37
     38def freeze(instance: object) -> object:
     39    """recursively freeze an object in-place so that its attributes and elements cannot be changed
     40
     41    messy in the sense that sometimes the object is modified in place, but you can't rely on that. always use the return value.
     42
     43    the [gelidum](https://github.com/diegojromerolopez/gelidum/) package is a more complete implementation of this idea
     44
     45    """
     46
     47    # mark as frozen
     48    if hasattr(instance, "_IS_FROZEN"):
     49        if instance._IS_FROZEN:
     50            return instance
     51
     52    # try to mark as frozen
     53    try:
     54        instance._IS_FROZEN = True  # type: ignore[attr-defined]
     55    except AttributeError:
     56        pass
     57
     58    # skip basic types, weird things, or already frozen things
     59    if isinstance(instance, (bool, int, float, str, bytes)):
     60        pass
     61
     62    elif isinstance(instance, (type(None), type(Ellipsis))):
     63        pass
     64
     65    elif isinstance(instance, (FrozenList, FrozenDict, frozenset)):
     66        pass
     67
     68    # handle containers
     69    elif isinstance(instance, list):
     70        for i in range(len(instance)):
     71            instance[i] = freeze(instance[i])
     72        instance = FrozenList(instance)
     73
     74    elif isinstance(instance, tuple):
     75        instance = tuple(freeze(item) for item in instance)
     76
     77    elif isinstance(instance, set):
     78        instance = frozenset({freeze(item) for item in instance})
     79
     80    elif isinstance(instance, dict):
     81        for key, value in instance.items():
     82            instance[key] = freeze(value)
     83        instance = FrozenDict(instance)
     84
     85    # handle custom classes
     86    else:
     87        # set everything in the __dict__ to frozen
     88        instance.__dict__ = freeze(instance.__dict__)  # type: ignore[assignment]
     89
     90        # create a new class which inherits from the original class
     91        class FrozenClass(instance.__class__):  # type: ignore[name-defined]
     92            def __setattr__(self, name, value):
     93                raise AttributeError("class is frozen")
     94
     95        FrozenClass.__name__ = f"FrozenClass__{instance.__class__.__name__}"
     96        FrozenClass.__module__ = instance.__class__.__module__
     97        FrozenClass.__doc__ = instance.__class__.__doc__
     98
     99        # set the instance's class to the new class
    100        try:
    101            instance.__class__ = FrozenClass
    102        except TypeError as e:
    103            raise TypeError(
    104                f"Cannot freeze:\n{instance = }\n{instance.__class__ = }\n{FrozenClass = }"
    105            ) from e
    106
    107    return instance

class FrozenDict(builtins.dict):
View Source
View on GitHub

     5class FrozenDict(dict):
     6    def __setitem__(self, key, value):
     7        raise AttributeError("dict is frozen")
     8
     9    def __delitem__(self, key):
    10        raise AttributeError("dict is frozen")

##### Inherited Members

builtins.dict
  get
  setdefault
  pop
  popitem
  keys
  items
  values
  update
  fromkeys
  clear
  copy

class FrozenList(builtins.list):
View Source
View on GitHub

    13class FrozenList(list):
    14    def __setitem__(self, index, value):
    15        raise AttributeError("list is frozen")
    16
    17    def __delitem__(self, index):
    18        raise AttributeError("list is frozen")
    19
    20    def append(self, value):
    21        raise AttributeError("list is frozen")
    22
    23    def extend(self, iterable):
    24        raise AttributeError("list is frozen")
    25
    26    def insert(self, index, value):
    27        raise AttributeError("list is frozen")
    28
    29    def remove(self, value):
    30        raise AttributeError("list is frozen")
    31
    32    def pop(self, index=-1):
    33        raise AttributeError("list is frozen")
    34
    35    def clear(self):
    36        raise AttributeError("list is frozen")

Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.

def append(self, value):
View Source
View on GitHub

    20    def append(self, value):
    21        raise AttributeError("list is frozen")

Append object to the end of the list.

def extend(self, iterable):
View Source
View on GitHub

    23    def extend(self, iterable):
    24        raise AttributeError("list is frozen")

Extend list by appending elements from the iterable.

def insert(self, index, value):
View Source
View on GitHub

    26    def insert(self, index, value):
    27        raise AttributeError("list is frozen")

Insert object before index.

def remove(self, value):
View Source
View on GitHub

    29    def remove(self, value):
    30        raise AttributeError("list is frozen")

Remove first occurrence of value.

Raises ValueError if the value is not present.

def pop(self, index=-1):
View Source
View on GitHub

    32    def pop(self, index=-1):
    33        raise AttributeError("list is frozen")

Remove and return item at index (default last).

Raises IndexError if list is empty or index is out of range.

def clear(self):
View Source
View on GitHub

    35    def clear(self):
    36        raise AttributeError("list is frozen")

Remove all items from list.

##### Inherited Members

builtins.list
  list
  copy
  index
  count
  reverse
  sort

def freeze(instance: object) -> object:
View Source
View on GitHub

     39def freeze(instance: object) -> object:
     40    """recursively freeze an object in-place so that its attributes and elements cannot be changed
     41
     42    messy in the sense that sometimes the object is modified in place, but you can't rely on that. always use the return value.
     43
     44    the [gelidum](https://github.com/diegojromerolopez/gelidum/) package is a more complete implementation of this idea
     45
     46    """
     47
     48    # mark as frozen
     49    if hasattr(instance, "_IS_FROZEN"):
     50        if instance._IS_FROZEN:
     51            return instance
     52
     53    # try to mark as frozen
     54    try:
     55        instance._IS_FROZEN = True  # type: ignore[attr-defined]
     56    except AttributeError:
     57        pass
     58
     59    # skip basic types, weird things, or already frozen things
     60    if isinstance(instance, (bool, int, float, str, bytes)):
     61        pass
     62
     63    elif isinstance(instance, (type(None), type(Ellipsis))):
     64        pass
     65
     66    elif isinstance(instance, (FrozenList, FrozenDict, frozenset)):
     67        pass
     68
     69    # handle containers
     70    elif isinstance(instance, list):
     71        for i in range(len(instance)):
     72            instance[i] = freeze(instance[i])
     73        instance = FrozenList(instance)
     74
     75    elif isinstance(instance, tuple):
     76        instance = tuple(freeze(item) for item in instance)
     77
     78    elif isinstance(instance, set):
     79        instance = frozenset({freeze(item) for item in instance})
     80
     81    elif isinstance(instance, dict):
     82        for key, value in instance.items():
     83            instance[key] = freeze(value)
     84        instance = FrozenDict(instance)
     85
     86    # handle custom classes
     87    else:
     88        # set everything in the __dict__ to frozen
     89        instance.__dict__ = freeze(instance.__dict__)  # type: ignore[assignment]
     90
     91        # create a new class which inherits from the original class
     92        class FrozenClass(instance.__class__):  # type: ignore[name-defined]
     93            def __setattr__(self, name, value):
     94                raise AttributeError("class is frozen")
     95
     96        FrozenClass.__name__ = f"FrozenClass__{instance.__class__.__name__}"
     97        FrozenClass.__module__ = instance.__class__.__module__
     98        FrozenClass.__doc__ = instance.__class__.__doc__
     99
    100        # set the instance's class to the new class
    101        try:
    102            instance.__class__ = FrozenClass
    103        except TypeError as e:
    104            raise TypeError(
    105                f"Cannot freeze:\n{instance = }\n{instance.__class__ = }\n{FrozenClass = }"
    106            ) from e
    107
    108    return instance

recursively freeze an object in-place so that its attributes and elements cannot be changed

messy in the sense that sometimes the object is modified in place, but you can't rely on that. always use the return value.

the gelidum package is a more complete implementation of this idea