Skip to content

Instantly share code, notes, and snippets.

@RhetTbull
Last active February 8, 2020 20:17
Show Gist options
  • Save RhetTbull/410bd2f002899b2cde86b54c18710ef8 to your computer and use it in GitHub Desktop.
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
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