Last active
February 8, 2020 20:17
-
-
Save RhetTbull/410bd2f002899b2cde86b54c18710ef8 to your computer and use it in GitHub Desktop.
A simple record class that can be made immutable (frozen) but also behaves like a mutable named tuple when not frozen
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from types import SimpleNamespace | |
class FreezableRecord(SimpleNamespace): | |
""" A simple namespace record class that allows attributes to be made immutable (frozen) after creation | |
Must be initialized with allowed attributes at creation via **kwargs | |
Behaves somewhat like a namedtuple in that once initialized, new attributes are not permitted, | |
however, unlike a nametuple, attribute values are mutable (unless frozen) | |
Attempt to set a new attribute after initialization will raise AttributeError """ | |
def __init__(self, **kwargs): | |
self.__frozen = False | |
super().__init__(**kwargs) | |
self.__init = True | |
def __setattr__(self, name, value): | |
# if instance is being initiliazed, allow any attribute to be set | |
# if instance not frozen, allow only existing attribute to be set | |
# if frozen, don't allow any attributes to be set | |
frozen = f"_{type(self).__name__}__frozen" | |
init = f"_{type(self).__name__}__init" | |
if ( | |
frozen in self.__dict__ | |
and self.__frozen | |
and name in self.__dict__ | |
and name != frozen | |
): | |
# frozen and trying to set allowed attribute | |
raise AttributeError("Cannot set attribute while frozen") | |
elif init in self.__dict__ and name not in self.__dict__: | |
raise AttributeError("Cannot set new attributes after __init__") | |
else: | |
super().__setattr__(name, value) | |
def freeze(self): | |
self.__frozen = True | |
def thaw(self): | |
self.__frozen = False | |
def main(): | |
rec = FreezableRecord(x=None, y=None) | |
# attribute values are mutable | |
rec.x = 1 | |
rec.y = 2 | |
# repr inherited from SimpleNamespace | |
print(rec) | |
# but cannot add new attributes | |
try: | |
rec.z = 3 | |
except Exception as e: | |
print(f"Caught exception creating new attribute: {e}") | |
# freeze | |
rec.freeze() | |
# now values are immutable | |
try: | |
rec.x = 3 | |
except Exception as e: | |
print(f"Caught exception setting during freeze: {e}") | |
# still can't add a new attribute | |
try: | |
rec.z = 3 | |
except Exception as e: | |
print(f"Caught exception creating new attribute: {e}") | |
# thaw | |
rec.thaw() | |
# attributes are now mutable again | |
rec.x = 3 | |
print(rec) | |
# nice __eq__ inherited from SimpleNamespace | |
rec2 = FreezableRecord(x=3, y=2) | |
print(rec == rec2) # True | |
if __name__ == "__main__": | |
main() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment