pythoninheritancedictionarygetset

How to "perfectly" override a dict?


How can I make as "perfect" a subclass of dict as possible? The end goal is to have a simple dict in which the keys are lowercase.

It would seem that there should be some tiny set of primitives I can override to make this work, but according to all my research and attempts it seem like this isn't the case:

Here is my first go at it, get() doesn't work and no doubt there are many other minor problems:

class arbitrary_dict(dict):
    """A dictionary that applies an arbitrary key-altering function
       before accessing the keys."""

    def __keytransform__(self, key):
        return key

    # Overridden methods. List from 
    # https://stackoverflow.com/questions/2390827/how-to-properly-subclass-dict

    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    # Note: I'm using dict directly, since super(dict, self) doesn't work.
    # I'm not sure why, perhaps dict is not a new-style class.

    def __getitem__(self, key):
        return dict.__getitem__(self, self.__keytransform__(key))

    def __setitem__(self, key, value):
        return dict.__setitem__(self, self.__keytransform__(key), value)

    def __delitem__(self, key):
        return dict.__delitem__(self, self.__keytransform__(key))

    def __contains__(self, key):
        return dict.__contains__(self, self.__keytransform__(key))


class lcdict(arbitrary_dict):
    def __keytransform__(self, key):
        return str(key).lower()

Solution

  • You can write an object that behaves like a dict quite easily with ABCs (Abstract Base Classes) from the collections.abc module. It even tells you if you missed a method, so below is the minimal version that shuts the ABC up.

    from collections.abc import MutableMapping
    
    
    class TransformedDict(MutableMapping):
        """A dictionary that applies an arbitrary key-altering
           function before accessing the keys"""
    
        def __init__(self, *args, **kwargs):
            self.store = dict()
            self.update(dict(*args, **kwargs))  # use the free update to set keys
    
        def __getitem__(self, key):
            return self.store[self._keytransform(key)]
    
        def __setitem__(self, key, value):
            self.store[self._keytransform(key)] = value
    
        def __delitem__(self, key):
            del self.store[self._keytransform(key)]
    
        def __iter__(self):
            return iter(self.store)
        
        def __len__(self):
            return len(self.store)
    
        def _keytransform(self, key):
            return key
    

    You get a few free methods from the ABC:

    class MyTransformedDict(TransformedDict):
    
        def _keytransform(self, key):
            return key.lower()
    
    
    s = MyTransformedDict([('Test', 'test')])
    
    assert s.get('TEST') is s['test']   # free get
    assert 'TeSt' in s                  # free __contains__
                                        # free setdefault, __eq__, and so on
    
    import pickle
    # works too since we just use a normal dict
    assert pickle.loads(pickle.dumps(s)) == s
    

    I wouldn't subclass dict (or other builtins) directly. It often makes no sense, because what you actually want to do is implement the interface of a dict. And that is exactly what ABCs are for.