Python already provides immutable versions of many of the mutable built-in types. Dict is the notable exception. Regardless, here is a protocol that objects may implement that facilitates turning immutable object mutable and vice-versa.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | """freeze module
The freeze and unfreeze functions special-case some of the built-in
types. If these types grow the appropriate methods then that will
become unnecessary.
"""
__all__ = ("Freezable", "UnFreezable", "freeze", "unfreeze")
import builtins
from abc import ABCMeta, abstractmethod
class Freezable(metaclass=ABCMeta):
@abstractmethod
def __freeze__(self):
"""Returns an immutable version of this object."""
class UnFreezable(metaclass=ABCMeta):
@abstractmethod
def __unfreeze__(self):
"""Returns a mutable version of this object."""
def freeze(obj):
"""Returns the immutable version of the object."""
if hasattr(type(obj), "__freeze__"):
return obj.__freeze__()
try:
handler = _freeze_registry[type(obj)]
except KeyError:
pass
else:
return handler(obj)
#if hasattr(type(obj), "__unfreeze__"):
# return obj
msg = "Don't know how to freeze a {} object"
raise TypeError(msg.format(type(obj)))
def unfreeze(obj, strict=False):
if hasattr(type(obj), "__unfreeze__"):
return obj.__unfreeze__()
try:
handler = _unfreeze_registry[type(obj)]
except KeyError:
pass
else:
return handler(obj)
#if hasattr(type(obj), "__freeze__"):
# return obj
msg = "Don't know how to unfreeze a {} object"
raise TypeError(msg.format(type(obj)))
#################################################
# special-casing built-in types
_freeze_registry = {}
_unfreeze_registry = {}
def register(f, cls=None):
action, typename = f.__name__.split("_")
if cls is None:
cls = getattr(builtins, typename)
if action == "freeze":
_freeze_registry[cls] = f
Freezable.register(cls)
elif action == "unfreeze":
_unfreeze_registry[cls] = f
UnFreezable.register(cls)
else:
raise TypeError
return f
@register
def freeze_dict(obj):
raise NotImplementedError
@register
def unfreeze_dict(obj):
return obj
@register
def freeze_list(obj):
return tuple(obj)
@register
def unfreeze_list(obj):
return obj
@register
def freeze_tuple(obj):
return obj
@register
def unfreeze_tuple(obj):
return list(obj)
|
Important Note
After I submitted this recipe, I found that there is an almost identical PEP for this idea, PEP 351. That PEP was rejected for concerns expressed in this thread. From the little I've read so far I'm not entirely sure it's as bad as all that. I'll update this note if further reading changes my mind.
[update: yeah, the arguments against it center on hashing (not so applicable) and on the idea not being not worth the trouble. If you find this recipe useful, leave a note, maybe with a real-life use-case. At the least, better alternatives for the use-case could develop. At best, the recipe gets validated! :)]
This topic has come up a few times on the various Python mailing lists in the few years that I have been following, so it's certainly not a terribly original idea of mine. I will note, however, that this recipe is strictly the result of a stream-of-consciousness hack-it-out episode. I was pleasantly surprised that the FLUFL had come up with a nearly identical idea over 6 years ago. :)
Hashable
I originally had Freezable as a subclass of collections.abc.Hashable, but figured a frozen object is not necessarily hashable and vice-versa. However, PEP 351 (and related commentary) seems to tie Freezable to Hashable as an invariant. I'm still not sure about that, but I'll be the first to concede that those folks know a lot more than me!
Examples
>>> freeze({})
Traceback (most recent call last):
...
NotImplementedError
>>> unfreeze({})
{}
>>> class X(dict):
... def __freeze__(self):
... return tuple(sorted(self.items()))
...
>>> freeze(X(a=1, b=2))
(('a', 1), ('b', 2))