1

I'm looking to create a defaultdict who's key automatically refers to an integer or float of equal value

ie behavior would be:

In [1]: selfkey = defaultdict(something)

In [2]: selfkey[4]
Out[2]: 4

In [3]: selfkey[800]
Out[3]: 800

I'm not sure if this is possible and I'm also not sure if this is the same question here: Is there a clever way to pass the key to defaultdict's default_factory?

^I don't understand if the wording is asking for the same thing that I'm looking for.

I'd think there would be a recursive answer somehow but I'm not having any luck trying to implement a lambda function that can get the value to refer to itself.

The closest I've gotten is to refer back to the keys of the dict itself, but this extends itself when you pass a second value:

In [50]: t = defaultdict(lambda: t.keys())

In [51]: t[4]
Out[51]: dict_keys([4])

In [52]: t[5]
Out[52]: dict_keys([4, 5])

Any ideas?

Estif
  • 182
  • 1
  • 10
  • Your approach would stop working if you reuse the variable `t` for something else (while keeping another name to the defaultdict). What would be your use case? Can't you use `selfkey.setdefault(x, x)`? – mozway May 24 '22 at 13:56
  • @mozway I have a bunch of identified-values that refer to associated quantities, but sometimes an identified-value should refer to the quantity of a different identified-value. I want to use this as a way to keep track of what should refer to its own quantity vs that of something else. This would return the key to a different dict. – Estif May 24 '22 at 14:00
  • Can you provide a minimal example? – mozway May 24 '22 at 14:01
  • Since Python 3.7 dicts are ordered collections, so you could, In theory access the last key in your lambda function to assign the default value, but I couldn't make it work. However, the question you linked is actually what you need. Using that answer, you should pass an identity function to the default_factory function, like `lambda key: key` – scespinoza May 24 '22 at 14:04
  • 1
    @scespinoza have you tested what you suggest? The function passed to `defaultdict` expects no parameter – mozway May 24 '22 at 14:11
  • @scespinoza `Traceback (most recent call last): File "", line 1, in TypeError: () missing 1 required positional argument: 'key'` – wwii May 24 '22 at 14:12
  • @mozway the problem I have with providing a minimal example is it heavily relies on the logic I'm using to make the decision behind what value refers to what quantity. It would be simplified to leave that out, and in the process trying to use this sort of self-referring defaultdict as a middleman between two dicts. It's about as minimal as I can make it. I also haven't gotten the code in that other example I linked to actually work either. – Estif May 24 '22 at 14:16
  • I was suggesting using the answer linked in the OP (https://stackoverflow.com/questions/2912231/is-there-a-clever-way-to-pass-the-key-to-defaultdicts-default-factory) not the default implementation of `defaultdict`. – scespinoza May 24 '22 at 14:18
  • @scespinoza in this case yes, but I thought this was out-ruled by OP ;) – mozway May 24 '22 at 14:38

2 Answers2

2

Using the solution proposed in Is there a clever way to pass the key to defaultdict's default_factory?.

from collections import defaultdict

class keydefaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError( key )
        else:
            ret = self[key] = self.default_factory(key)
            return ret

d = keydefaultdict(lambda key: key)
print(d[4])
print(d[5])

This outputs

4
5
scespinoza
  • 396
  • 3
  • 10
2

You can just create your own dict class and modify the __getitem__ method like this:

class mydict(dict):
    def __getitem__(self, x):
        if x not in self:
            return x
        else:
            return super().__getitem__(x)

This class has the behavior you want

>>> d = mydict()
>>> d[3]
3
>>> d[1.1]
1.1
>>> d[5] = -2
>>> d[5]
-2

EDIT: if you want the defaultdict behavior of adding the missing key to the dictionary, you can do this like so:

class mydict(dict):
    def __getitem__(self, x):
        if x not in self:
            self[x] = x
            return x
        else:
            return super().__getitem__(x)
jprebys
  • 2,469
  • 1
  • 11
  • 16