[Python-ideas] Revisiting Immutable Mappings

Steven D'Aprano steve at pearwood.info
Thu Oct 11 18:16:00 EDT 2018


On Fri, Oct 12, 2018 at 02:45:30AM +1100, Chris Angelico wrote:
> On Fri, Oct 12, 2018 at 2:41 AM Chris Barker - NOAA Federal via
> Python-ideas <python-ideas at python.org> wrote:
> >
> > > This violates the Liskov Substitution Principle.
> >
> > If we REALLY had a time machine, then dict would subclass frozendict,
> > and we’d be all set.
> 
> Thanks to virtual subclassing, we can still do this. The question is, should we?
> 
> Intuition tells me that a frozen dictionary is a form of dictionary
> that adds restrictions, not that a dictionary is a frozen dictionary
> that you left out to thaw.

No offence Chris, but that's why we shouldn't program by intuition :-) 
We don't build Smart cars by starting with a Hummer and slicing off the 
bits that aren't needed.

We already have prior art demonstrating best practice in the form 
of the Mapping and MutableMapping ABCs, and frozenset versus set for 
concrete classes.

Best practice is not to subclass a type and restrict existing 
functionality, but to start with a restricted class and extend
functionality. Or not to subclass at all.

Subclassing is not the only way to DRY -- there are other ways for 
frozendict and dict to share code other than subclassing, although 
they're not necessarily efficient or easy if frozendict is written in 
pure Python. One way is delegation to a hidden dict (like the 
MappingProxyType, except we ensure the dict is private and not shared).


# Just a sketch, not a full implementation.
class frozendict(object):
    def __new__(cls, *args, **kwargs):
        instance = super().__new__(cls)
        instance.__d = dict(*args, **kwargs)
        return instance

    # Define only the methods we want, delegating to the
    # hidden dict.

    def __getitem__(self, key):
        return self.__d[key]

    def keys(self):
        return self.__d.keys()

    def items(self):
        return self.__d.items()


The trickiest part is probably getting hashing right. I think this would 
work:

    def __hash__(self):
        return hash(tuple(self.items()))



> But as we see from [frozen]set, it's
> probably best to treat them as completely independent classes, both
> implementing the basic Mapping interface.

Indeed.


-- 
Steve


More information about the Python-ideas mailing list