I need a dictionary that is automatically filled with a default value for each accessed key that is missing. I've found defaultdict and some other ways to achieve this, but the problem in my case is that I want the default value for each key to be specific to the key itself.
For example, with defaultdict I can achieve something like this:
from collections import defaultdict
d = defaultdict(lambda: 5)
> d[1] = 3
> d[1]
> 3
> d[2]
> 5
But what if I need the default value for each accessed missing key to be for example key + 5? Something like:
from collections import defaultdict
d = defaultdict(lambda key: key + 5) # <-- This does not work as defaultdict expects lambda function to be without any parameters
> d[1] = 3
> d[1]
> 3
> d[2]
> 7 <- Calculated from accessed key + 5 (2+5)
> d[5]
> 10 <- Calculated from accessed key + 5 (5+5)
Is there a clean, builtin way to achieve what I need? I know that I can create a subclass of dict and implement this specific functionality at __getitem__ level, but I want to avoid that if possible.
I couldn't find a solution in other answers, so sorry if it is still a duplicate.
5 Answers 5
I don't think there is a builtin way of doing this. However, instead of subclassing dict and change getitem, you can subclass defaultdict itself to tell __missing__() to call the default_factory with an argument (the key), rather than without args. It is still a custom class, not a builtin, but it should be quite efficient still.
from collections import defaultdict
class DefaultDict(defaultdict):
def __missing__(self, key):
return self.default_factory(key)
Then:
d = DefaultDict(lambda key: key + 5)
d[2]
# 7
1 Comment
(I do not have enough reputation to comment, so I am commenting here)
Pierre's top answer does work for setting and getting values in the defaultdict, but it does not add the k-v pair to the dict, so you cannot iterate through all the keys, values, or items.
Instead, add self[key] = self.default_factory(key)
from collections import defaultdict
class DefaultDict(defaultdict):
def __missing__(self, key):
self[key] = self.default_factory(key)
return self[key]
Comments
Another simpler way, not relying on defaultdict
Sub classing dict and relying on overriding the __missing__ method
class DefaultDict(dict):
def __init__(self, key_fn):
super().__init__()
self._key_fn = key_fn
def __missing__(self, key):
v = self._key_fn(key)
self[key] = v
return v
d = DefaultDict(lambda key: f"{key}-is-the-key")
puts(d[2])
for k, v in d.items():
print(k, v)
Or if you want with python typing
K = TypeVar('K')
V = TypeVar('V')
class DefaultDict(dict[K, V]):
def __init__(self, key_fn: Callable[[K], V]):
super().__init__()
self._key_fn = key_fn
def __missing__(self, key: K) -> V:
v = self._key_fn(key)
self[key] = v
return v
d: DefaultDict[int, str] = DefaultDict(lambda key: f"this-is-the-{key}")
print(d[2])
Comments
You can also use a function like this
dic = {}
DEFAULT_VALUE = 5
def dict_get(item):
try:
return [dic[item]]
except:
dic[int(item)] = DEFAULT_VALUE + int(item)
return DEFAULT_VALUE + int(item)
print(dict_get(10))
1 Comment
Here is how to do it without creating any class. You can use dict.setdefault:
d = {}
d_default_factory = lambda key: key + 5
d.setdefault(2, d_default_factory(2))
# 7
Optionally, you could save the default factory as an entry in the dictionary, like so:
d = {'default_factory': lambda key: key + 5}
d.setdefault(2, d['default_factory'](2))
# 7
Comments
Explore related questions
See similar questions with these tags.
defaultdictonly works with lambda functions without parameters, so the second example is not a working python code.