2

I want to create a class inheriting from dict type but could use case-insensitive key to visit the data. I implement a simple one but I don't think using instance.__dict__ variable is a proper approach.
Is there a better way to do this?

Here is my code:

class MyDict(dict):
    def __init__(self, *args, **kwargs):
        if args:
            for k, v in args[0].iteritems():
                self.__dict__.update({k.lower(): v})

    def __getitem__(self, k):
        return self.__dict__.get(k.lower())

    def __setitem__(self, k, v):
        self.__dict__.update({k.lower(): v})

    def __delitem__(self, k):
        self.__dict__.pop(k, None)


if __name__ == '__main__':
    test_0 = MyDict({'naME': 'python', 'Age': 24})
    print(test_0['name'])    # return 'python'
    print(test_0['AGE'])     # return 24

    test_1 = MyDict()
    test_1['StaCk'] = 23
    print(test_1['stack'])   # return 23
    print(test_1['STACK'])   # return 23
flyer
  • 9,280
  • 11
  • 46
  • 62
  • 1
    [`super()`](http://docs.python.org/library/functions.html#super) is the preferred way to accesss ancestors' methods. – Lev Levitsky Dec 12 '13 at 12:33
  • 3
    See [How to “perfectly” override a dict](http://stackoverflow.com/q/3387691/222914) – Janne Karila Dec 12 '13 at 12:41
  • Really do see that question. The accepted answer is a better way to implement a case-insensitive dictionary than inheriting from `dict`. Aside from anything else you want `isinstance(MyDict(), dict)` to be false because *your class does not have the documented behaviour of `dict`*). Liskov substitution principle. – Steve Jessop Dec 12 '13 at 12:44
  • @SteveJessop None of the provided subclasses of `dict` follow LSP either. e.g. `defaultdict` fails to throw an exception on a missing key, and comparison of two `OrderedDict` instances can return not equal where as `dict` instances they would compare equal. – Duncan Dec 12 '13 at 13:18
  • @Duncan: fair point. Since the standard libraries have already created a problem it doesn't matter if user-defined classes participate in it. ABCs are also a better way to implement `defaultdict` and `OrderedDict`, but that ship sailed. – Steve Jessop Dec 12 '13 at 14:34
  • @Duncan, OrderedDict.__eq__() comparison to a regular dict is order-insensitive, so LSP is preserved. See my example at http://pastebin.com/cFCAewFw – tbc0 Sep 23 '14 at 18:23

1 Answers1

4

Edit: See Janne Karila's link, that contains a better solution.

Instead of using self.__dict__, which has a special meaning unrelated to this being a dict, you should use super() to call the corresponding function on the superclass.

E.g.,

def __setitem__(self, k, v):
    if hasattr(k, 'lower'):
        k = k.lower()
    return super(MyDict, self).__setitem__(k, v)
Community
  • 1
  • 1
RemcoGerlich
  • 30,470
  • 6
  • 61
  • 79
  • IMHO, this is a situation where it would be better to leave the `hasattr()` check out and just `return super(MyDict, self).__setitem__(k.lower(), v)` and allow an exception to be raised if appropriate -- the [EAFP vs LBYL](http://stackoverflow.com/a/3734681/355230) debate. Also note the answer in Janne Karila's link doesn't have any error checking. – martineau Dec 12 '13 at 14:14
  • Matter of taste I guess. In my opinion, just that a dictionary is case-insensitive shouldn't mean that non-string keys stop working. – RemcoGerlich Dec 12 '13 at 14:15
  • In that case you could still assume it's a string with a `lower()` method and catch the exception inside the method. – martineau Dec 12 '13 at 14:19
  • @martineau: there's a problem with EAFP that usually (and I think in this case) is pretty minor but still annoying. Suppose that `lower` is implemented but throws `AttributeError`, then you don't necessarily want to catch it, it's probably better to let it propagate. So I think it is a matter of taste, the question is whether you document that `k` is used unmodified as the key "if calling `k.lower()` throws `AttributeError`" vs "if `lower` doesn't exist", vs, "if `lower` doesn't exist or is not callable", or whatever. So it's not about code structure, it's about preferred behaviour. – Steve Jessop Dec 12 '13 at 14:43
  • @SteveJessop: Seems to mecould just document that if the key has a `lower()` method that can be called without raising an `AttributeError` exception then it will be. Secondly, an EAFP style version would simply be `try:`, `return super(MyDict, self).__setitem__(k.lower(), v)`, `except AttributeError:`, `return super(MyDict, self).__setitem__(k, v)`. – martineau Dec 12 '13 at 15:00