You can use the __new__
method to create a wrapper around frozenset
. I quote the doc:
new() is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. It is also commonly overridden in custom metaclasses in order to customize class creation.
The idea is to cache every wrapper created and to return always the same instance for the same frozenset
.
There is a little trick: elements of the frozenset
that are themselves frozenset
s, they should be wrapped too.
class FrozenSetWrapper:
_cache = {}
def __new__(cls, iterable=[]):
fs = frozenset(FrozenSetWrapper(e) if isinstance(e, frozenset) else e
for e in iterable) # wrap recursively
fsw = FrozenSetWrapper._cache.get(fs)
if fsw is None: # was not in cache
fsw = super(FrozenSetWrapper, cls).__new__(cls) # create an object
fsw._fs = fs # init
fsw.__doc__ = fs.__doc__
FrozenSetWrapper._cache[fs] = fsw # cache
return fsw # and return
Examples:
f1 = FrozenSetWrapper([1,2,3])
f2 = FrozenSetWrapper([1,2,3])
print(f1, f2)
# <__main__.FrozenSetWrapper object at 0x7f7894f2fa90> <__main__.FrozenSetWrapper object at 0x7f7894f2fa90>
Now, we have to reimplement the methods of frozenset
to get a perfect match. This is easy for some of them: just delegate the work to the wrapped frozenset
:
def __repr__(self):
return self._fs.__repr__()
def __iter__(self):
return self._fs.__iter__()
...
But for some methods, you have to handle frozenset
and FrozenSetWrapper
:
def __contains__(self, e):
elif isinstance(e, frozenset):
e = FrozenSetWrapper(e)
return self._fs.contains(e)
def __eq__(self, other):
if isinstance(other, FrozenSetWrapper):
return self is other
elif isinstance(other, frozenset)
return self._fs == other
else:
return False
...
Or the return type:
def __and__(self, other):
if isinstance(other, FrozenSetWrapper):
return FrozenSetWrapper(self._fs.__and__(other._fs))
elif isinstance(other, frozenset):
return FrozenSetWrapper(self._fs.__and__(other))
else:
raise TypeError("unsupported operand type(s) ...")
The idea of interning makes sense, but the implementation may be tricky because of the edge cases.