One way is to use the abstract base class collections.abc.MutableMapping, this way, you only need to override the abstract methods and then you can be sure that access always goes through your logic -- you can do this with dict too, but for example, overriding dict.__setitem__ will not affect dict.update, dict.setdefault etc... So you have to override those by hand too. Usually, it is easier to just use the abstract base class:
from collections.abc import MutableMapping
from enum import Enum
class Color(Enum):
RED = "RED"
GREEN = "GREEN"
BLUE = "BLUE"
class ColorDict(MutableMapping):
def __init__(self): # could handle more ways of initializing but for simplicity...
self._data = {}
def __getitem__(self, item):
return self._data[color]
def __setitem__(self, item, value):
color = self._handle_item(item)
self._data[color] = value
def __delitem__(self, item):
del self._data[color]
def __iter__(self):
return iter(self._data)
def __len__(self):
return len(self._data)
def _handle_item(self, item):
try:
color = Color(item)
except ValueError:
raise KeyError(item) from None
return color
Note, you can also add:
def __repr__(self):
return repr(self._data)
For easier debugging.
An example in the repl:
In [3]: d = ColorDict() # I want to implement a ColorDict class such that ...
...:
...: d[Color.RED] = 123
...: d["RED"] = 456 # I want this to override the previous value
...: d[Color.RED] # ==> 456
Out[3]: 456
In [4]: d["foo"] = 789 # I want this to produce an KeyError exception
...:
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-4-9cf80d6dd8b4> in <module>
----> 1 d["foo"] = 789 # I want this to produce an KeyError exception
<ipython-input-2-a0780e16594b> in __setitem__(self, item, value)
17
18 def __setitem__(self, item, value):
---> 19 color = self._handle_item(item)
20 self._data[color] = value
21
<ipython-input-2-a0780e16594b> in _handle_item(self, item)
34 color = Color(item)
35 except ValueError:
---> 36 raise KeyError(item) from None
37 return color
38 def __repr__(self): return repr(self._data)
KeyError: 'foo'
In [5]: d
Out[5]: {<Color.RED: 'RED'>: 456}