1

I want to define a defaultdict, so that if the key is missing, its value should be f'You can set {key} to inject configuration here

Consider the following code

from collections import defaultdict

# Valid, but the default value for all key will be empty string.
d1 = defaultdict(str)  
d1['post_simulation']  # return ''

# This is what I intend to do, 
# but it is invalid as the default_factory won't take any arguments.
d2 = defaultdict(lambda k: f'#You can inject text here by setting {k}')
d2['post_simulation'] 
# TypeError: <lambda>() missing 1 required positional argument: 'k'

Is there any tech that I can do this in Python by using defaultdict or other data structure?

link89
  • 1,064
  • 10
  • 13

1 Answers1

3

Yes, by overriding the default __missing__ method:

from collections import defaultdict


class MyDefaultDict(defaultdict):
    def __missing__(self, key):
        self[key] = f'#You can inject text here by setting {key}'
        return self[key]


d = MyDefaultDict()
print(d["foo"])
# => #You can inject text here by setting foo

You can generalise it like this:

from collections import defaultdict


class LambdaDefaultDict(defaultdict):
    def __init__(self, missing, *args, **kwargs):
        super().__init__(None, *args, **kwargs)
        self.missing = missing

    def __missing__(self, key):
        self[key] = self.missing(key)
        return self[key]


d = LambdaDefaultDict(lambda k: f'#You can inject text here by setting {k}')
print(d["foo"])

There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

There has to be a better name for this than LambdaDefaultDict.

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • 2
    Also at this point there's not too much value extending the `defaultdict` class at all. It would be simpler to subclass `dict` directly – flakes Aug 02 '23 at 01:36
  • Do you know why `default_factory` of `defaultdict` doesn't take key as its argument? – link89 Aug 02 '23 at 01:45
  • 2
    @link89 I believe it's just for simplicity in the most common usecases, such as `defaultdict(list)`, `defaultdict(dict)`, `defaultdict(someothercontainer)`, where the factory provided is a no-arg constructor. Needing to reference the key you are supplying is a more niche usecase, and it would force everyone to write their code like `defaultdict(lambda _: list())` – flakes Aug 02 '23 at 01:51
  • 1
    I do not. As a guess, lack of foresight and/or prioritising succintness. – Amadan Aug 02 '23 at 01:51
  • Also if that was the default behavior `defaultdict(list)['foo']` would result in `['f', 'o', 'o']` – flakes Aug 02 '23 at 01:53
  • In the more niche usecase, you have the `__missing__` method available, so you can still achieve what you want with only a few extra definitions. – flakes Aug 02 '23 at 01:54