[Python-ideas] Revisiting Immutable Mappings

Steven D'Aprano steve at pearwood.info
Thu Oct 11 07:41:40 EDT 2018


On Thu, Oct 11, 2018 at 02:18:52AM -0700, Zaur Shibzukhov wrote:
> 
> May be the following simple prototype of frozendict could be useful?
> 
> def frozen_error():
>    return RuntimeError("frozendict is not mutable")
> 
> class frozendict(dict):

This violates the Liskov Substitution Principle. 

https://en.wikipedia.org/wiki/Liskov_substitution_principle

Why is that bad? Let's say you have a function that requires a dict, 
and following the principle of "Fail Fast", you check the input up 
front:

def spam(adict):
    """Input:
        adict:     an instance of dict
    """
    if isinstance(adict, dict):
       # lots of code here
       # and deep inside the function, we end up calling:
        adict[key] = "something"

I hear people objecting that isinstance checks are "unPythonic". 
Perhaps; however, the basic principle applies regardless of whether we 
are doing LBYL isinstance checks, test for the presence of a __setitem__ 
method, or do no up-front tests at all and rely on duck-typing and EAFP.

Our spam() function now accepts dicts, and rejects non-dicts, and works 
well, until somebody passes a frozendict:

spam(frozendict())

This passes the isinstance test (or the check for __setitem__). It 
passes the documented interface: frozendicts certainly are dicts, since 
they are a subclass of dict. But nevertheless, the call to setitem 
fails, and the function breaks.

We can get a good idea of how this ought to work by looking at set and 
frozenset. frozenset is *not* a subclass of set; it is a distinct class, 
so that frozensets are not sets. Rather than having mutator methods 
which raise an exception, they simply don't provide those methods at 
all:

py> f = frozenset()
py> isinstance(f, set)
False
py> hasattr(f, 'add')
False


That's how we should design frozendict.


-- 
Steve


More information about the Python-ideas mailing list