-1

I am implementing a class that inherits from UserDict. This class acts completely as expected but I would like to extend its features. I would like it to pass through all methods that have been overriden when acting upon the dictionary. An example would be:

from collections import UserDict


class DummyClass(UserDict):
    def __init__(self):
        self.mine = {}
        self.data = self.mine

    def __getitem__(self, key):
        print("Doing some stuff here")
        return self.data[key]

    def __setitem__(self, key, value):
        print(f"Uses my __setitem__ {key} {value}")
        self.data[key] = value

When doing:

dd = DummyClass()
dd["one"] = {"two": 2}
print(f"First dict: {dd}") 

The output is:

Uses my __setitem__ one {'two': 2}
First dict: {'one': {'two': 2}}

However,

dd["one"].update({"two": 4})
print(f"New dict: {dd.keys()}")

will use the modified getitem and use the base method update.

Doing some stuff here
New dict: {'one': {'two': 4}}

What I would like is that dd["one"] were an object that could be modified calling the modified setitem without having to do dd.update({"one": {"two": 4}})

My question is which is the best way to go about this? In the example above, should I return an object with getitem, modify it and return it? I would like it to be as generic as possible.

It is closely related to question:

Subclassing Python dictionary to override __setitem__

But I was wondering if there is a new approach to this now.

Any help is appreciated

L. Segui
  • 26
  • 6
  • 2
    The values *aren't* `DummyClass` instances, they're vanilla dictionaries, so it's unclear why this is surprising. – jonrsharpe Jun 13 '18 at 12:22
  • 2
    Your class is really confusing. Why are you deriving from `UserDict` in the first place? And why are you doing `self.data = self.__dict__`? This will allow to access the items in the dictionary via attribute access syntax, but it will also conflate the dictionary with the attributes, so e.g. the dictionary will always have a key "data". Overall, it's rather unclear to me what you are trying to achieve, but I'm pretty sure you would be better off using a class that does not derive from `UserDict` or `dict`, and instead simply uses a dictionary to store the data. – Sven Marnach Jun 13 '18 at 12:27
  • I am creating an API where the output is a dictionary that I'd like to manipulate as naturally as possible (reason for UserDict). I tried to provide a MWE from scratch and I agree it is inconsistent as it is. The idea is to be able to fill the dictionary through assignments but also do something like above instead of `dd.update({"one": {"two": 4}})` – L. Segui Jun 13 '18 at 12:53
  • 1
    When you do `dd["one"].update({"two": 4})`, you're not calling `update` on `dd`, but rather on the value returned by `dd.__getitem__("one")` (which is the normal dictionary `{"two": 2}` in this case). If you want to wrap your values in another level of custom custom dictionary, you need to do that explicitly (with e.g. `dd["one"] = DummyClass({"two": 2})`), or write code to implicitly do the wrapping for you (perhaps in `__getitem__`). – Blckknght Jun 13 '18 at 13:01
  • I see that I didn't explain myself clearly, sorry for that. I am not surprised by the behaviour of the class, what I would like if possible, is that dd["one"] returned an object that I could manipulate using a set of custom methods and return it to the class dictionary without having to wrap it explicitly as suggested by @Blckknght. – L. Segui Jun 13 '18 at 13:25

1 Answers1

1

You can wrap the inner dictionaries you're given as values in another instance of your class. If you want this to happen automatically, I'd suggest doing it for any dictionary value you're passed in __setitem__, since that lets you mutate the wrapper object in place later, while you keep a reference to it in your data.

Try something like this:

def __setitem__(self, key, value):
    print(f"Uses my __setitem__ {key} {value}")
    if isisnstance(value, dict):
        wrapper = DummyClass()  # if __init__ accepts arguments use: value = DummyClass(value)
        wrapper.update(value)
        value = wrapper
    self.data[key] = value

Example output:

>>> dd = DummyClass()

>>> dd["one"] = {"two": 2}
Uses my __setitem__ one {'two': 2}
Uses my __setitem__ two 2

>>> dd["one"].update({"two": 4})
Doing some stuff here
Uses my __setitem__ two 4

>>> print(dd)
{'one': {'two': 4}}
Blckknght
  • 100,903
  • 11
  • 120
  • 169