2

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?

null
  • 404
  • 6
  • 12
  • By "hash" do you mean "dictionary"/``dict``? The term hash is not commonly used to describe a hashtable in Python. – MisterMiyagi Aug 05 '20 at 22:03
  • 1
    Is there a reason why you want ``MyDict`` to be contagious, instead of actually defining the values to be of appropriate type – e.g ``deep_dict = MyDict(data={'a': MyDict(data={'b': 'value'})})``? It is not common for types to promote their members, e.g. an ``OrderedDict`` of ``dict``s will not promote its members to ``OrderedDict``. Note that if you do not overwrite ``__init__``, you can use the ``dict`` initialiser syntax as in ``deep_dict = MyDict(a = MyDict(b = 'value'))``. – MisterMiyagi Aug 05 '20 at 22:08
  • Some cases allows you to assign obj.__class__ = class to change the type, https://stackoverflow.com/questions/3464061/cast-base-class-to-derived-class-python-or-more-pythonic-way-of-extending-class/3464154 but in this case, you will get `TypeError: __class__ assignment only supported for heap types or ModuleType subclasses` – James Lin Aug 05 '20 at 22:18
  • @MisterMiyagi yes by hash I mean dict. A dictionary is a hash implementation. – null Aug 05 '20 at 22:31
  • @MisterMiyagi Yes I could change the original dict object into multiple objects of type `MyDict`. It would be a solution. And I'll think about not promoting members. My specific case is a generic dict configuration that handles some predefined keys and values in a special way (user and passwords). – null Aug 05 '20 at 22:45

1 Answers1

1

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.

James Lin
  • 25,028
  • 36
  • 133
  • 233
  • Yes. This solves my issue. The only downside is having to implement all methods that dict implements. I'll accept it as is. – null Aug 05 '20 at 22:46