Earlier today, I read the question "Raise error if python dict comprehension overwrites a key" and decided to try my hand at an answer. The method that naturally occurred to me was to subclass dict
for this. However, I got stuck on my answer, and now I'm obsessed with getting this worked out for myself.
Notes:
namedtuple
or a regular dictionary wherever I have a requirement for something like this.class DuplicateKeyError(KeyError):
pass
class UniqueKeyDict(dict):
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
def __setitem__(self, key, value):
if key in self: # Validate key doesn't already exist.
raise DuplicateKeyError('Key \'{}\' already exists with value \'{}\'.'.format(key, self[key]))
super().__setitem__(key, value)
def update(self, *args, **kwargs):
if args:
if len(args) > 1:
raise TypeError('Update expected at most 1 arg. Got {}.'.format(len(args)))
else:
try:
for k, v in args[0]:
self.__setitem__(k, v)
except ValueError:
pass
for k in kwargs:
self.__setitem__(k, kwargs[k])
>>> ukd = UniqueKeyDict((k, int(v)) for k, v in ('a1', 'b2', 'c3', 'd4')) # Should succeed.
>>> ukd['e'] = 5 # Should succeed.
>>> print(ukd)
{'a': 1, 'b': 2, 'c': 3, d: 4, 'e': 5}
>>> ukd['a'] = 5 # Should fail.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in __setitem__
__main__.DuplicateKeyError: Key 'a' already exists with value '1'.
>>> ukd.update({'a': 5}) # Should fail.
>>> ukd = UniqueKeyDict((k, v) for k, v in ('a1', 'b2', 'c3', 'd4', 'a5')) # Should fail.
>>>
I'm certain the issue is in my update()
method, but I'm not able to determine just what I'm doing wrong.
Below is the original version of my update()
method. This version fails as expected on duplicates when calling my_dict.update({k: v})
for a key/value pair already in the dict, but does not fail when including a duplicate key while creating the original dict, due to the fact that converting the args to a dict
results in default behavior for a dictionary, i.e., overwriting the duplicate key.
def update(self, *args, **kwargs):
for k, v in dict(*args, **kwargs).items():
self.__setitem__(k, v)
To override a dict with Python, we can create a subclass of the MutableMapping class. to create a TransformedDict class that is a subclass of the MutableMapping . We use a dict as the value of the store instance variable.
With CPython 2.7, using dict() to create dictionaries takes up to 6 times longer and involves more memory allocation operations than the literal syntax. Use {} to create dictionaries, especially if you are pre-populating them, unless the literal syntax does not work for your case.
The update() method updates the dictionary with the elements from another dictionary object or from an iterable of key/value pairs.
The dict() function creates a dictionary. A dictionary is a collection which is unordered, changeable and indexed.
It's interesting that simply overriding __setitem__
is not enough to change the behavior of update
in dict
. I would have expected that dict
would use its __setitem__
method when it's being updated using update
. In all cases, I think it's better to implement collections.MutableMapping
to achieve the desired result without touching update
:
import collections
class UniqueKeyDict(collections.MutableMapping, dict):
def __init__(self, *args, **kwargs):
self._dict = dict(*args, **kwargs)
def __getitem__(self, key):
return self._dict[key]
def __setitem__(self, key, value):
if key in self:
raise DuplicateKeyError("Key '{}' already exists with value '{}'.".format(key, self[key]))
self._dict[key] = value
def __delitem__(self, key):
del self._dict[key]
def __iter__(self):
return iter(self._dict)
def __len__(self):
return len(self._dict)
Edit: included dict
as base class to satisfy the isinstance(x, dict)
check.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With