1

I am running Python 2.7.10.

I would like to have a dictionary return the value stored at a particular key in case of missing item. For example, something like that:

myD = dict(...)
return myD[key] if key in myD else myD[defaultKey]

Just to make sure it is clear, I want to call myD[key] and have the right value returned without the extra if...else in my code...

This isn't quite what defaultdict does (since it takes a function to call as a default) and not quite what dict.setdefault() does, and myD.get(key, ???) does not seem to help either. I probably should inherit from dict or defaultdict and overload __init__() and missing() methods, but I could not come up with a good way to do this.

13
  • What's your question, exactly? You're right that dict.get won't do that; if you want to know how you could write your own, look at docs.python.org/2/library/… and implement __getitem__ accordingly. But where should defaultKey come from (is it a parameter? An attribute of the class, or of the instance?) Commented Oct 28, 2015 at 22:50
  • @jonrsharpe yes, that's my question, how to implement this. I am not sure how to use the ABCs in this context Commented Oct 28, 2015 at 22:52
  • @jonrsharpe yes, defaultKey is param at initialization, that the instance should store as an attribute Commented Oct 28, 2015 at 22:54
  • You inherit them, then implement the methods they specify (in this case __[get/set/del]item__, __iter__ and __len__, plus your custom __init__ for the default key-value pair). Commented Oct 28, 2015 at 22:54
  • @jonrsharpe i was hoping i could get something easier by inheriting from a ready-made object, like defaultdict, and just overriding __getitem__, leaving same functionality everywhere else... Commented Oct 28, 2015 at 22:58

3 Answers 3

5

In your case, dict.get should work(I know you already mentioned it didn't work). Did you try:

myD.get(key,myD[defaultKey])
Sign up to request clarification or add additional context in comments.

5 Comments

yes, that would work, but I need to make a class that would do that by default without me specifying it explicitly in my calling code
@gt6989b so, again, what is your question? If it's just "how do I write that?" it's too broad for SO.
Have you tried creating a class that inherits defaultdict but overrides the defaultfactory method?
I agree with Robert. In that case, create your own implementation of a dictionary and override its .get() to the above code
The defaultfactory actually changes the dict to have a new thing. get returns a reference to an existing thing. So depending on what you want...
1

I'm not completely sure what you want (didn't read all the comments under your question), but think this may be at least close to what you want.

class DefaultKeyDict(dict):
    def __init__(self, default_key, *args, **kwargs):
        self.default_key = default_key
        super(DefaultKeyDict, self).__init__(*args, **kwargs)

    def __missing__ (self, key):
        if self.default_key not in self:  # default key not defined
            raise KeyError(key)
        return self[self.default_key]

    def __repr__(self):
        return ('{}({!r}, {})'.format(self.__class__.__name__,
                                      self.default_key,
                                      super(DefaultKeyDict, self).__repr__()))

    def __reduce__(self):  # optional, for pickle support
        args = (self.default_key if self.default_key in self else None,)
        return self.__class__, args, None, None, self.iteritems()

dkdict = DefaultKeyDict('b', {'a': 1, 'b': 2, 'c': 3})

print dkdict['a']  # -> 1
print dkdict['b']  # -> 2
print dkdict['c']  # -> 3
print dkdict['d']  # -> 2 (value of the default key)

del dkdict['b']  # delete the default key
try:
    print dkdict['d']  # should now raise exception like normal
except KeyError:
    print("dkdict['d'] raised a KeyError")

You might want to modify the class __init__() method to accept both the default key and its value as arguments (instead of just the key).

4 Comments

thanks, i was toying with this for a while to make it work, couldn't do it right... thank you very much. If i want to pass an arg default_key in __init__() as you suggest, the signature would change to def __init__(self, default_key, *args, **kwargs)? Or do I just check if default_key is in kwargs (if so, how can i distinguish this from someone actually wanting a pair like this in the dictionary itself)?
For the record, the canonical sentinel (for when None is inappropriate due to being a valid value) is to just do sentinel = object() (sentinel can be Null if you like, but I find sentinel to describe the purpose more directly) and perform all comparisons to it with is (mostly for efficiency; object's __eq__ is an identity check anyway). No need to create a relatively expensive class to define the sentinel type; object can be constructed directly to get a unique marker using almost no resources (object inst. is a mere 16 bytes on Py 3.5, vs. 1016B+56B for class + instance).
@ShadowRanger: Point taken...although I'm not overly concerned with the additional memory usage since only one instance is ever created. It's not a sentinel in the sense that it doesn't mark the beginning or end of anything, so I didn't give it that name.
@gt6989b: You could make the signature def __init__(self, default_key, default_key_value, *args, **kwargs) or make it a keyword argument. To distinguish the latter from a user key/value pair, the keyword could start with an underscore, i.e. _default_key=('DefaultKey', 42). Doing something like that is, by convention, how private names are usually designated in Python.
1

When overwriting __getitem__, one can use simply square brackets. It returns the value for the first valid key and None if no key is found.

class mDict(dict):
    def __getitem__(self, keys):
        for k in keys:
            if k in self:
                return self.get(k)

mdict = mDict({'a': 1, 'b': 2, 'default': 3})

mdict['a', 'default']        # -> 1
mdict['X', 'b', 'default']   # -> 2
mdict['X', 'Y', 'default']   # -> 3
mdict['X', 'Y', 'Z']         # -> None

One can use here also more than just two keys, which is more readable than many nested .get().

1 Comment

Not sure this really answers the question, though

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.