0

I wrote a python class that inherit the dict builtin type. I want to write a sort of container with additional check methods.

If I create an instance of that class, I got a RecursionError. I do not understand this error.

class Params(dict):
    """ A convenient container of all paramters """

    # list of authorized keywords with default values
    keywords = {"k1": 1, "k2": 2, "k3": 3}

    def __init__(self, *args, **kwargs):
        # self.update(self.keywords)
        for k, v in self.keywords.items():
            self[k] = v
        self.update(*args, **kwargs)

    def update(self, *args, **kwargs):
        for k, v in dict(*args, **kwargs).items():
            self[k] = v

    def __setitem__(self, name, value):
        """ set a parameters following dict syntax """
        name = name.lower()
        if name in self.keywords:
            self[name] = value
        else:
            raise KeyError("Keywords %s does not exist." % name)

    def __getattr__(self, name):
        """ get paramters as an attribute of the class """
        return self.__getitem__(name)
Ger
  • 9,076
  • 10
  • 37
  • 48
  • 2
    What do you think `self[name] = value` is telling Python to do? – user2357112 Feb 05 '18 at 23:23
  • naively, `self` is a dict, thus python add a new key with a given value. But this is certainly wrong :(. I do not understand the link between `self` and `self.keywords` except that `keywords` is a class attribute. – Ger Feb 05 '18 at 23:31
  • 1
    You should check this out: https://stackoverflow.com/questions/3387691/how-to-perfectly-override-a-dict – Anthony Kong Feb 05 '18 at 23:33
  • 1
    @Ger `self[name]` calls `self.__setitem__`.... which you do in `__setitem__` thus the recursion. – juanpa.arrivillaga Feb 05 '18 at 23:34
  • That means that I have to use the `__setitem__` method of the super class (the dict class) ? – Ger Feb 05 '18 at 23:43
  • Precisely! Note, most of the nice syntactical featurins like `some_object[x]` have hooks in the language to be overriden, allowing you to implement them for your own custom classes. – juanpa.arrivillaga Feb 05 '18 at 23:44
  • @AnthonyKong, Yes I tried – Ger Feb 05 '18 at 23:45

1 Answers1

1

So, as I hinted in the comments, your recursion error is happening because you are implicitely calling self.__setitem__ in self.__setitem__, so you should call the superclass __setitem__ instead. Here's one way:

In [6]: class Params(dict):
   ...:     """ A convenient container of all paramters """
   ...:
   ...:     # list of authorized keywords with default values
   ...:     keywords = {"k1": 1, "k2": 2, "k3": 3}
   ...:
   ...:     def __init__(self, *args, **kwargs):
   ...:         # self.update(self.keywords)
   ...:         for k, v in self.keywords.items():
   ...:             self[k] = v
   ...:         self.update(*args, **kwargs)
   ...:
   ...:     def update(self, *args, **kwargs):
   ...:         for k, v in dict(*args, **kwargs).items():
   ...:             self[k] = v
   ...:
   ...:     def __setitem__(self, name, value):
   ...:         """ set a parameters following dict syntax """
   ...:         name = name.lower()
   ...:         if name in self.keywords:
   ...:             # note: dict.__setitem__(self, name, value) would also work
   ...:             super().__setitem__(name, value)
   ...:         else:
   ...:             raise KeyError("Keywords %s does not exist." % name)
   ...:
   ...:     def __getattr__(self, name):
   ...:         """ get paramters as an attribute of the class """
   ...:         return self.__getitem__(name)
   ...:

In [7]: p = Params(k1=10)

In [8]: p
Out[8]: {'k1': 10, 'k2': 2, 'k3': 3}

Note, I used the Python 3 version of super Python 2 would require: super(Params, self).__setitem__(name, value)

Note, in general I would prefer composition over inheritance. Have a Params object with an internal dict attribute which you manage through a public interface. This will be more robust and avoid the headaches that come with inheriting from built-in types.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172