8

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.

sahinakkaya
6,0852 gold badges25 silver badges48 bronze badges
asked Jan 26, 2021 at 12:59
4
  • Why do you think your current way is not clean? Commented Jan 26, 2021 at 13:16
  • defaultdict only works with lambda functions without parameters, so the second example is not a working python code. Commented Jan 26, 2021 at 13:24
  • It's just showing what kind of functionality I want to end up with Commented Jan 26, 2021 at 13:25
  • Oh, OK. I thought this is the current behavior and you want further improvements. Commented Jan 26, 2021 at 13:26

5 Answers 5

9

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
answered Jan 26, 2021 at 13:25
Sign up to request clarification or add additional context in comments.

1 Comment

Even though I wanted to avoid creation of a subclass, this seems like the best solution for now, so I'll accept this answer, thanks!
3

(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]
answered May 31, 2024 at 6:06

Comments

2

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])
answered Aug 29, 2024 at 12:21

Comments

1

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))
sahinakkaya
6,0852 gold badges25 silver badges48 bronze badges
answered Jan 26, 2021 at 13:29

1 Comment

The problem with this approach is that dic is searched twice for the key (if the key is missing): first time it searches for it when you try to return, then it searches again when you insert the key (it's the default behaviour: whenever you try to insert a key the dict first checks if the key is already present or not). If the dict is huge, it matters. defauldict from the standard library avoids this double search.
-1

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
answered Oct 26, 2022 at 0:27

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.