How to force calling __getitem__ using items() and values() in dict inheritance in python

Viewed 342

I use python3.7 and for some application create a class inherited from a dict, but have a problem to implement items() and values() methods to made it work correctly. This class overrides many methods, but here I placed a very simplified example just to illustrate exact problem:

class MyFunction:
    def __call__(self):
        return 5

class MyDict(dict):
    def __getitem__(self, key):
        item = super().__getitem__(key)
        if isinstance(item, MyFunction):
            return item()
        else:
            return item

    def get(self, key, default=None):
        if self.__contains__(key):
            return self.__getitem__(key)
        if isinstance(default, MyFunction):
            return default()
        return default

    # def __copy__(self):
    #     return type(self)(self)
    # 
    # def copy(self):
    #     return self.__copy__()

    # def __iter__(self):
    #     return super().__iter__()


d = MyDict(a=MyFunction(), b=3)

I want than I get a value by key instances of MyFunction be called. This works well:

for k in d:
    print(k, d[k])

and prints the expected output:

a 5
b 3

But these two do not:

for v in d.values():
    print(v)

for k, v in d.items():
    print(k, v)

They print the function's repr.

How can I achive them to call __getitem__?

Remark: It can be some kind of dict built-in class optimization (I would like to not inherit form UserDict or Mapping). For example if I uncomment:

def  __iter__(self):
     return super().__iter__()

The calls:

new_d = d.copy()
new_d = dict(d)
new_d = dict(**d)

will call the __getitem__

1 Answers

According to the Python documentation, accessing a dict-like object with the d[k] syntax is just syntactic sugar for d.__getitem__(k).

However, as you discovered, the default implementation of values() and items() do not call __getitem__() at all. If you want them to, you'll have to implement them yourself.

Here is an implementation that hopefully does what you want:

class MyFunction:
    def __call__(self):
        return 5

class MyDict(dict):
    def __getitem__(self, key):
        item = super().__getitem__(key)
        if isinstance(item, MyFunction):
            return item()
        else:
            return item

    def values(self):
        for k in self:
            yield(self[k])

    def items(self):
        for k in self:
            yield(k, self[k])

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default

d = MyDict(a=MyFunction(), b=3)

for k in d:
    print(d.get(k))

for v in d.values():
    print(v)

for k, v in d.items():
    print(k, v)

This is the output:

5
3
5
3
a 5
b 3
Related