3
\$\begingroup\$

What do you think of this idea? It's supposed to be a dict that makes a reasonable guess at the key you were trying to access for getters and deleters. Setters should be untouched.

Similar to how tab completion in bash works, if some command you are typing only has one reasonable completion then pressing tab will fill it out for you.

The logic should be like this, if the query_key is not in the dict but there is only one dict_key satisfying the expression query_key in dict_key then we can assume that is the key. For string keys, additionally, it should be case-insensitive.

class FriendlyDict(dict):

    def _guess_key_or_die(self, q):
        possibilities = []
        for key in self:
            try:
                if q in key or q.lower() in key.lower():
                    possibilities.append(key)
            except (TypeError, AttributeError):
                pass
        if len(possibilities) == 1:
            return possibilities[0]
        err = KeyError(q)
        err.data = possibilities
        raise err

    def __getitem__(self, key):
        try:
            return super(FriendlyDict, self).__getitem__(key)
        except KeyError:
            guess = self._guess_key_or_die(key)
            return super(FriendlyDict, self).__getitem__(guess)

    def __delitem__(self, key):
        try:
            return super(FriendlyDict, self).__delitem__(key)
        except KeyError:
            guess = self._guess_key_or_die(key)
            return super(FriendlyDict, self).__delitem__(guess)

    def __contains__(self, key):
        if super(FriendlyDict, self).__contains__(key):
            return True
        try:
            self._guess_key_or_die(key)
        except KeyError:
            return False
        else:
            return True
  1. Are there any more __dunder__ methods which really should be overridden?
  2. Are there any gotchas I may have missed which might make the class behave in weird/unpredictable ways?
  3. Are there any other nice features that could be added which are consistent with the general idea of this?
  4. Is it better as a subclass or would it be possible to make it a mixin to use with other types of mappings, e.g. OrderedDict?
\$\endgroup\$
4
\$\begingroup\$

A few other methods I would suggest implementing:

  • get;
  • setdefault;
  • has_key (if supporting 2.x); and
  • __repr__/__str__.

There is an edge case on hashable collections (tuple, frozenset):

>>> d = FriendlyDict()
>>> d[(1, 2)] = "foo"
>>> d[1]
'foo'
>>> d[frozenset(("bar", "baz"))] = 3
>>> d["bar"]
3

Also equality comparisons might be a problem - should two FriendlyDicts be considered equal if all (most?) of their keys are "close enough" and have the same values?


In terms of additional features, consider whether numerical keys should be matched with some tolerance - dictionaries not handling floats particularly well could be one reason to use this class. Also, a missing letter within a string key might be something to pick up (e.g. d["parameer"]).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.