muutils.misc.freezing
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):
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):
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
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: object) -> object:
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