Hi everyone. This is another one of those “frozen/freezable object” thoughts. Most user-defined “immutable” objects are made via subclassing and overriding __setattr__ (or the more convenient @dataclasses.dataclass(frozen=True)). This works well when the object as a whole must be read-only throughout most of its lifetime. As a consequence, internal code that does mutate the object (usually via object.__setattr__()) becomes more verbose and harder to maintain.
Currently, values of the object-level __dict__ attribute are limited to Python dict instances. Allowing users to set other types of mappings such as frozendict and MappingProxyType could be useful for creating frozen objects and read-only proxies for passing data between threads.
An object which is only mutable during initialization:
class Point:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
self.__dict__ = frozendict(self.__dict__)
An object that supports creating read-only views:
import types
class Rect:
def __init__(self, /, x1: int, y1: int, x2: int, y2: int) -> None:
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
def __eq__(self, other, /, *, MISSING=object()) -> bool:
return self.__dict__ == getattr(other, "__dict__", MISSING)
def as_view(self, /) -> Rect:
view = self.__new__(__class__)
view.__dict__ = types.MappingProxyType(self.__dict__)
return view
rect = Rect(0, 0, 800, 400)
rect_view = rect.as_view()
rect.x2 = 1900
rect.y2 = 1080
assert rect == rect_view
rect_view.x1 = 600 # error -- mappingproxy is read-only
An object which is only mutable when holding the object’s lock:
import types
import threading
class Point:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
self._lock = threading.Lock()
self._dict = self.__dict__
self._lock_state()
def _unlock_state(self):
self.__dict__ = self._dict
def _lock_state(self):
self.__dict__ = types.MappingProxyType(self._dict)
def __enter__(self):
self._lock.acquire()
self._unlock_state()
def __exit__(self, exc_type=None, exc=None, traceback=None):
self._lock_state()
self._lock.release()