I've created a subclass of Python's builtin dictionary dict
. The subclass allows dictionaries to have multiple keys with the same value. Hence the name "homogeneous". Behind the scenes, I'm using a normally dictionary. If a key has more than one value, then it's values are implemented with a list. Otherwise, the key, value pair is normal.
Here are some example usages:
>>> d = HomogeneousDict([('a', 1), ('b', 2), ('c', 3), ('b', 4)])
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'b': 4, 'a': 1})
>>> d['a']
1
>>> d['b']
[2, 4]
>>> d['a'] = 5
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'b': 4, 'a': 1, 'a': 5})
>>> d['a']
[1, 5]
>>> len(d)
5
>>> d['subdict'] = HomogeneousDict([('d', 6), ('e', 7)])
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'b': 4, 'a': 1, 'a': 5, 'subdict': HomogeneousDict({'e': 7, 'd': 6})})
>>> d['subdict'] = 8
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'b': 4, 'a': 1, 'a': 5, 'subdict': HomogeneousDict({'e': 7, 'd': 6}), 'subdict': 8})
>>> d['subdict']
[HomogeneousDict({'e': 7, 'd': 6}), 8]
>>> del d['subdict']
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'b': 4, 'a': 1, 'a': 5})
>>> d.pop('b', 1) # Pop the second value of the 'b' key
4
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'a': 1, 'a': 5})
>>> for k, v in d.items():
print('{} => {}'.format(k, v))
c => 3
b => 2
a => 1
a => 5
>>>
I haven't given much thought if there is a practically usage for this type of dictionary. I really made this as more of a hobby project, rather than out of necessity.
Here's the source code. I documented the important parts of the code, so their shouldn't be much to explain or figure out. It's actually only about 120 LOCs:
class HomogeneousDict(dict):
"""
About
-----
A dictionary that allows multiple keys to have
like values.
The dictionary supports all of the same methods of a normally
dictionary. The chief difference between a normally dictonary and
a HomogeneousDict, is when you get items.
If a key has be given multiple values, a list of all
of the values are returned for that key. Otherwise, the
key's value is returned normally as is.
Examaples
---------
>>> d = HomogeneousDict([('a', 1), ('b', 2), ('c', 3), ('b', 4)])
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'b': 4, 'a': 1})
>>> d['a']
1
>>> d['b']
[2, 4]
>>> d['a'] = 5
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'b': 4, 'a': 1, 'a': 5})
>>> d['a']
[1, 5]
>>> len(d)
5
>>> d['subdict'] = HomogeneousDict([('d', 6), ('e', 7)])
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'b': 4, 'a': 1, 'a': 5, 'subdict': HomogeneousDict({'e': 7, 'd': 6})})
>>> d['subdict'] = 8
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'b': 4, 'a': 1, 'a': 5, 'subdict': HomogeneousDict({'e': 7, 'd': 6}), 'subdict': 8})
>>> d['subdict']
[HomogeneousDict({'e': 7, 'd': 6}), 8]
>>> del d['subdict']
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'b': 4, 'a': 1, 'a': 5})
>>> d.pop('b', 1) # Pop the second value of the 'b' key
4
>>> d
HomogeneousDict({'c': 3, 'b': 2, 'a': 1, 'a': 5})
>>> for k, v in d.items():
print('{} => {}'.format(k, v))
c => 3
b => 2
a => 1
a => 5
>>>
"""
def __init__(self, values):
"""
Parameters
----------
values : list
List of tuples that represent key, value pair
"""
for key, value in values:
self.__setitem__(key, value)
def __setitem__(self, key, value):
if key in self:
if isinstance(self[key], list):
self[key].append(value)
else:
super().__setitem__(key, [self.pop(key), value])
else:
super().__setitem__(key, value)
def __getitem__(self, key):
return super().__getitem__(key)
def __len__(self):
count = 0
for key, value in super().items():
if isinstance(value, list):
count += len(value)
else:
count += 1
return count
def __repr__(self):
key_value_reprs = []
for key, value in super().items():
if isinstance(value, list):
for element in value:
repr_ = '{}: {}'.format(key.__repr__(), element.__repr__())
key_value_reprs.append(repr_)
else:
repr_ = '{}: {}'.format(key.__repr__(), value.__repr__())
key_value_reprs.append(repr_)
return 'HomogeneousDict({%s})' % ', '.join(key_value_reprs)
def pop(self, key, index=0):
if isinstance(self[key], list):
value = self[key].pop(index)
if len(self[key]) == 1:
super().__setitem__(key, self[key].pop())
return value
else:
return super().pop(key)
def items(self):
for key, value in super().items():
if isinstance(value, list):
for element in value:
yield (key, element)
else:
yield key, value
The biggest question I have is: How well did I emulate a Python container type. Is there anything unintuitive or non-normally for a dictionary-like container type? Also, is there a better name for a dictionary that behaves like this?