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