2

I want to make a data object:

class GameData:
  def __init__(self, data={}):
    self.data = data

  def __getitem__(self, item):
    return self.data[item]

  def __setitem__(self, key, value):
    self.data[key] = value

  def __getattr__(self, item):
    return self.data[item]

  def __setattr__(self, key, value):
    self.data[kay] = value

  def __repr__(self):
    return str(self.data)

When I create a GameData object, I get RecursionError. How can I avoid setitem recall itself?

Just_Me
  • 111
  • 8
  • 2
    The problem is `__setattr__` – juanpa.arrivillaga May 21 '20 at 22:20
  • 1
    did you mean `key` in place of `kay` at `self.data[kay] = value` – Swetank Poddar May 21 '20 at 22:21
  • 1
    `self.__dict__['data'] = data` in `__init__` instead – Aaron May 21 '20 at 22:21
  • Does this answer your question? [How to properly subclass dict and override \_\_getitem\_\_ & \_\_setitem\_\_](https://stackoverflow.com/questions/2390827/how-to-properly-subclass-dict-and-override-getitem-setitem) – Rafael Barros May 21 '20 at 22:23
  • Beside the point, but that `__repr__` is bad. It will make it hard to debug your code if it looks like you're dealing with dicts but you're actually dealing with `GameData` instances. It would be OK as `__str__` though, I think. A good `__repr__` would be `return '{}({})'.format(type(self).__name__, str(self.data))` – wjandrea May 21 '20 at 22:55
  • Note that the problem actually has nothing to with your `__setitem__`. – wjandrea May 21 '20 at 23:06

1 Answers1

3

In the assignment self.data = data, __setattr__ is called because self has no attribute called data at the moment. __setattr__ then calls __getattr__ to obtain the non-existing attribute data. __getattr__ itself calls __getattr__ again. This is a recursion.

Use object.__setattr__(self, 'data', data) to do the assignment when implementing __setattr__.

class GameData:
  def __init__(self, data=None):
    object.__setattr__(self, 'data', {} if data is None else data)

  def __getitem__(self, item):
    return self.data[item]

  def __setitem__(self, key, value):
    self.data[key] = value

  def __getattr__(self, item):
    return self.data[item]

  def __setattr__(self, key, value):
    self.data[key] = value

  def __repr__(self):
    return str(self.data)

For details, see the __getattr__ manual

Additionally, do not use mutable objects as default parameter because the same object {} in the default argument is shared between GameData instances.

Aaron
  • 1,255
  • 1
  • 9
  • 12
  • Just curious, why do you use `object` instead of `super()`? – wjandrea May 21 '20 at 22:49
  • 1
    `super()` is a good choice, especially when inheritance is used. `object` ensure the default behavior of `__setattr__`. Updating the dict of the `__dict__` attribute can achieve the same goal as well. – Aaron May 21 '20 at 22:56
  • 1
    Inheritance is *always* used; `GameData` inherits from `object`. Failing to use `super` means you can't (easily) use `GameData` as a base class for other classes that *do* use `super`. There's no "default" definition of `__setattr__`; just the definition you inherit from an ancestor. – chepner May 22 '20 at 00:44
  • 1
    Further, `__setdata__` is *always* used to set an attribute, unlike `__getattr__` which is only used to get a value for an attribute not otherwise found via the normal lookup procedure. – chepner May 22 '20 at 00:45