Search code examples
pythonpython-3.xdictionarysubclassing

Subclass dict __getitem__ while maintaining original class type


I am trying to subclass a python dictionary __getitem__ method while maintaining the original class type when accessing the hash. No answer in properly subclassing or perfect overriding seemed to address this. For example,

class MyDict(dict):
    def __init__(self, data=None):
        if not data:
            data = {}
        dict.__init__(self, data)

    def __getitem__(self, key):
        if key == 'b':
            print('Found "b"')

        return dict.__getitem__(self, key)

shallow_dict = MyDict(data={
    'b': 'value'
})

# prints Found "b", as expected
x = shallow_dict['b']

deep_dict = MyDict(data={
    'a': {
        'b': 'value'
    }
})

# prints nothing
x = deep_dict['a']['b']

This happens because when we're accessing ['b'] we're actually accessing a dict, not MyDict anymore. So I tried to solve this by copying the content into a new object:

def __getitem__(self, key):
    data = dict.__getitem__(self, key)
    if key == 'b':
        print('Found "b"')

    if isinstance(data, dict):
        return MyDict(data)
    return data

However this solution lead to a new problem while writing content into the hash, because I'm returning a copy and not a reference:

deep_dict['a']['b'] = 'other value'

# prints 'value'
print(deep_dict['a']['b'])

Any suggestions on how to properly maintain the type, since copying had this side effect?


Solution

  • How about your MyDict just be a proxy for the dict:

    class MyDict(object):
        def __init__(self, data={}):
            self.data = data
    
        def __getitem__(self, key):
            if key == 'b':
                print('Found "b"')
            return MyDict(self.data.__getitem__(key))
    
        def __setitem__(self, key, value):
            return self.data.__setitem__(key, value)
    
        def __repr__(self):
            return self.data.__repr__()
    
        # add more __magic__ methods as you wish
    
    shallow_dict = MyDict({
        'b': 'value'
    })
    
    x = shallow_dict['b']
    
    
    deep_dict = MyDict({
        'a': {
            'b': 'value'
        }
    })
    
    x = deep_dict['a']['b']
    
    # assignment
    deep_dict['a']['a'] = {'b': 'here'}
    deep_dict['a']['a']['b']
    print(deep_dict)
    

    OUTPUT:

    Found "b"
    Found "b"
    Found "b"
    {'a': {'b': 'value', 'a': {'b': 'here'}}}
    

    As you can see when you get the self.data inside __getitem__ it just pass the result of self.data.__getitem__ by reference to a new MyDict object.