MattS
MattS

Reputation: 1711

Python - function like dict.get but returns value of default key instead of default value?

Let's say

a = {1:2, 3:4, 5:6}

Is there a builtin function (maybe something like a.get2(7,5)) that will return a[7], or a[5] if a[7] doesn't exist?

Such function can be defined easily as a.get(val, a.get(def_key)) but would prefer a builtin solution if exists.

Upvotes: 3

Views: 88

Answers (3)

Alain T.
Alain T.

Reputation: 42143

You could define a subclass of dict to access the dictionary entries with a virtual default value that will apply to any non-existing key but will not actually create any key when referencing them (as opposed to the setdefault() function)

 class dictWithDefault(dict):
     def __init__(self,aDict={},defaultValue=None):
         super().__init__(aDict)
         def aFunction():pass
         self._default = defaultValue if type(defaultValue) == type(aFunction) else lambda : defaultValue
     def __getitem__(self,key):
         return super().__getitem__(key) if key in self else self._default()

 d = {1:2, 3:4, 5:6}

 d = dictWithDefault(d,99)
 d[1] # >>> 2
 d[7] # >>> 99  this is the default value, but key 7 still doesn't exist
 d[7] = 97
 d[7] # >>> 97 once assigned the key has its own value
 d._default = 100 # you can change the virtual default at any time
 d[9] # >>> 100
 d[8] += 5   # this can be useful when key/values are used to count things
             # (using a default value of zero) akin to a Bag structure
 d[8] # >>> 105
 d    # >>> {1: 2, 3: 4, 5: 6, 7: 97, 8: 105}

You could also create the dictionary with a default value directly:

d = dictWithDefault({1:2, 3:4, 5:6},99)

To have a default key instead of a default value, you can use the same technique and just change the implementation of the getitem method.

Or, you can simply use d = dictWithDefault(d) without a default value and use the or operator to get to the alternative key(s):

d = dictWithDefault(d)
value = d[7] or d[5]

[EDIT] Changed code to support objects as default values.

You have to be careful with this when using objects (e.g. lists) as values for the dictionary. On the first implicit assignment to a new key, it would merely be assigned a reference to the _default object. This means that all these keys would end up referencing the same object.

For example: d = dictWithDefault(defaultValue=[]) will not work as expected.

d = dictWithDefault(defaultValue=[])

d["A"].append(1)
d["B"].append(2)

# will end up with { "A":[1,2], "A":[1,2] } 
# because both keys (A and B) reference the same list instance.

To work around this, I changed the function so it could accept a lambda: in cases where an object is used as a default.

This allows the default value to create a new instance when it is used for a new key.

d = dictWithDefault(defaultValue=lambda:[])

d["A"].append(1)
d["B"].append(2)

# now works properly giving: { "A":[1], "B":[2] }

You can still use the class without a lambda for simple types (strings, integers, ...) but you have to make sure to use a lambda: with an instance creation method when the dictionary stores objects as values.

Upvotes: 1

Jean-François Fabre
Jean-François Fabre

Reputation: 140148

you can subclass dict:

class MyDict(dict):
    def get2(self,*keys):
        for k in keys:
            if k in self:
                return self.get(k)
        return None  # if not found

a = {1:2, 3:4, 5:6}
b = MyDict(a)
print(b.get2(2,10,5))

The positional arguments allow to extend the behaviour to n keys. The general case cannot use get to know if the key is in the dict, as some values could be None hence the in test.

avoid double dict test with a sentinel object

class MyDict(dict):
    __notfound = object()
    def get2(self,*keys):
        for k in keys:
            x = self.get(k,self.__notfound )
            if x is not self.__notfound :
               return x
        return None  # if not found

Upvotes: 2

jpp
jpp

Reputation: 164623

Setting a constant fallback value is possible. One method is to use collections.defaultdict.

Note this requires creating a new dictionary. This, of course, we can assign to the same variable.

from collections import defaultdict

a = {1:2, 3:4, 5:6}

a = defaultdict(lambda: a[5], a)

This sets the default value to a constant 6, which will be returned when a key is not found. You will have to reset your default value each time a[5] is updated, if required.

Upvotes: 1

Related Questions