Need modified version of Python Dictionary - python

What methods need to be altered if want to change the default behaviour of the dictionary?
Some of the methods I am aware of like __getitem__(), __missing__(), __iter__() etc.
I am trying to implement the dictionary in such a way that if I tried to assign the value to key(already existed) then the old value should not go away while should be kept in some list and when we try to remove the key like pop(key), it should remove older value.
What methods need to be modified to override the dict class to achieve this behaviour?

It is the __setitem__ method that you want to update. You want it to create a list whenever a new key is set in your dictionary and append to that list if the key exists. You can then extend the __getitem__ method as well to take the index of the item you want in a list. As for the pop method, you will also need to override dict.pop.
class ListDict(dict):
def __setitem__(self, key, value):
if key not in self:
super().__setitem__(key, [])
self[key].append(value)
def __getitem__(self, item):
if isinstance(item, tuple):
item, pos = item
return super().__getitem__(item)[pos]
else:
return super().__getitem__(item)
def pop(self, k):
v = self[k].pop(0)
if not self[k]:
super().__delitem__(k)
return v
Example:
# Setting items
d = ListDict()
d['foo'] = 'bar'
d['foo'] = 'baz'
d # {'foo': ['bar', 'baz']}
# Getting items
d['foo', 0] # 'bar'
d['foo', 1] # 'baz'
d['foo', 0:2] # ['bar', 'baz']
# Popping a key
d.pop('foo')
d # {'foo': ['baz']}
d.pop('foo')
d # {}

Related

How to have multiple values for a key in a python dictionary?

I have a case where the same key could have different strings associated with it.
e.g. flow and wolf both have the same characters, if I sort them and use them as keys in a dictionary, I want to put the original strings as values.
I tried in a python dict as:
d = {}
d["flow"] = flow
d["flow"] = wolf
but there is only one value associated with the key.
I tried d["flow"].append("wolf") but that also doesn't work.
How to get this scenario working with Python dicts?
You can't have multiple items in a dictionary with the same key. What you should do is make the value a list. Like this -
d = dict()
d["flow"] = ["flow"]
d["flow"].append("wolf")
If that is what you want to do, then you might want to use defaultdict. Then you can do
from collections import defaultdict
d = defaultdict(list)
d["flow"].append("flow")
d["flow"].append("wolf")
You could use the setdefault method to create a list as the value for a key even if that key is not already in the dictionary.
So this makes the code really simple:
>>> d = {}
>>> d.setdefault(1, []).append(2)
>>> d.setdefault(1, []).append(3)
>>> d.setdefault(5, []).append(6)
>>> d
{1: [2, 3], 5: [6]}
You could implement a dict-like class that does exactly that.
class MultiDict:
def __init__(self):
self.dict = {}
def __setitem__(self, key, value):
try:
self.dict[key].append(value)
except KeyError:
self.dict[key] = [value]
def __getitem__(self, key):
return self.dict[key]
Here is how you can use it
d = MultiDict()
d['flow'] = 'flow'
d['flow'] = 'wolf'
d['flow'] # ['flow', 'wolf']

Multiples-keys dictionary where key order doesn't matter

I am trying to create a dictionary with two strings as a key and I want the keys to be in whatever order.
myDict[('A', 'B')] = 'something'
myDict[('B', 'A')] = 'something else'
print(myDict[('A', 'B')])
I want this piece of code to print 'something else'. Unfortunately, it seems that the ordering matters with tuples. What would be the best data structure to use as the key?
Use a frozenset
Instead of a tuple, which is ordered, you can use a frozenset, which is unordered, while still hashable as frozenset is immutable.
myDict = {}
myDict[frozenset(('A', 'B'))] = 'something'
myDict[frozenset(('B', 'A'))] = 'something else'
print(myDict[frozenset(('A', 'B'))])
Which will print:
something else
Unfortunately, this simplicity comes with a disadvantage, since frozenset is basically a “frozen” set. There will be no duplicate values in the frozenset, for example,
frozenset((1, 2)) == frozenset((1,2,2,1,1))
If the trimming down of values doesn’t bother you, feel free to use frozenset
But if you’re 100% sure that you don’t want what was mentioned above to happen, there are however two alternates:
First method is to use a Counter, and make it hashable by using frozenset again: (Note: everything in the tuple must be hashable)
from collections import Counter
myDict = {}
myDict[frozenset(Counter(('A', 'B')).items())] = 'something'
myDict[frozenset(Counter(('B', 'A')).items())] = 'something else'
print(myDict[frozenset(Counter(('A', 'B')).items())])
# something else
Second method is to use the built-in function sorted, and make it hashable by making it a tuple. This will sort the values before being used as a key: (Note: everything in the tuple must be sortable and hashable)
myDict = {}
myDict[tuple(sorted(('A', 'B')))] = 'something'
myDict[tuple(sorted(('B', 'A')))] = 'something else'
print(myDict[tuple(sorted(('A', 'B')))])
# something else
But if the tuple elements are neither all hashable, nor are they all sortable, unfortunately, you might be out of luck and need to create your own dict structure... D:
You can build your own structure:
class ReverseDict:
def __init__(self):
self.d = {}
def __setitem__(self, k, v):
self.d[k] = v
def __getitem__(self, tup):
return self.d[tup[::-1]]
myDict = ReverseDict()
myDict[('A', 'B')] = 'something'
myDict[('B', 'A')] = 'something else'
print(myDict[('A', 'B')])
Output:
something else
I think the point here is that the elements of the tuple point to the same dictionary element regardless of their order. This can be done by making the hash function commutative over the tuple key elements:
class UnorderedKeyDict(dict):
def __init__(self, *arg):
if arg:
for k,v in arg[0].items():
self[k] = v
def _hash(self, tup):
return sum([hash(ti) for ti in tup])
def __setitem__(self, tup, value):
super().__setitem__(self._hash(tup), value)
def __getitem__(self, tup):
return super().__getitem__(self._hash(tup))
mydict = UnorderedKeyDict({('a','b'):12,('b','c'):13})
mydict[('b','a')]
>> 12

How to return a class value when iterating through a dictionary of said class

In this simplified form, I want to return the value of bar1 when I iterator over a dictionary of a class in order to avoid issues with a library which requires a list.
class classTest:
def __init__(self, foo):
self.bar1 = foo
def __iter__(self):
for k in self.keys():
yield self[k].bar1
aDict = {}
aDict["foo"] = classTest("xx")
aDict["bar"] = classTest("yy")
for i in aDict:
print i
The current output is
foo
bar
I am targetting for this output to be
xx
yy
What am I missing to get this to work? Or is this even possible?
Your not iterating over the classes, but the dictionary. Also your class has no __getitem__-Method, so your __iter__ wouldn't even work.
To get your result you can do
for value in aDict.values():
print value.bar1
You're printing the keys. Print the values instead:
for k in aDict:
print aDict[k]
Or you can just iterate directly over the values:
for v in aDict.itervalues(): # Python 3: aDict.values()
print v
The __iter__ on your classTest class isn't being used because you're not iterating over a classTest object. (Not that it makes any sense as it's written.)

Proper way to iterate over a dict value that may or may not be present in Python

Basically, if I'm trying to access a dict value which I expect to be an iterable is there an easy one-liner to account for that value not being present aside from using some like DefaultDict. There's this
for el in (myDict.get('myIterable') or []):
pass
Doesn't feel particularly pythonic though...
for item in a_dict.get("some_key",[]):
#do whatever
if the item is guaranteed to be a list if present ... if it might be things other than a list you will need a different solution
You can make a subclass of dict that provides a default value with the __missing__(self, key) method:
class EmptyIterableDict(dict):
def __missing__(self, key):
return []
Example usage:
test = EmptyIterableDict()
test['a'] = [3,2,1]
test['b'] = [2,1]
test['c'] = [1]
for v in test['a']:
print v
3
2
1
for v in test['d']:
print v
If you already have a vanilla dict that you want to iterate like that over, you can make a temporary copy:
original = {'a': [1], 'b': [2,3]}
temp = EmptyIterableDict(original)
for v in temp['d']:
print v
An explicit, multi-line approach to this would be:
if 'my_iterable' in my_dict:
for item in my_dict['my_iterable']:
print(item)
which could also be written as a one-line comprehension:
[print(item) for item in my_dict['my_iterable'] if 'my_iterable' in my_dict]
This isn't a one-liner but it accounts for both possible failures.
try:
for item in dictionary[key]:
print(item)
except KeyError:
pass # Key wasn't present in the dictionary.
except TypeError:
pass # Key was present but corresponding item not iterable.

Overwrite {} in python

I want to make a dict int which you can access like that:
>>> my_dict["property'] = 3
>>> my_dict.property
3
So I've made this one:
class DictAsMember(dict):
def __getattr__(self, name):
return self[name]
This works fine, but if you have nested dicts it doesn't work, e.g:
my_dict = DictAsMember()
my_dict["property"] = {'sub': 1}
I can access to my_dict.property but logically I can't do my_dict.property.sub because property is a default dict, so what i want to do it's overwrite the default dict, so you can use {}.
Is this possible?
One workaround to the problem is wrapping default dictionaries using DictAsMember before returning them in the __getattr__ method:
class DictAsMember(dict):
def __getattr__(self, name):
value = self[name]
if isinstance(value, dict):
value = DictAsMember(value)
elif isinstance(value, list):
value = [DictAsMember(element)
if isinstance(element, dict)
else element
for element in value]
return value
my_dict = DictAsMember()
my_dict["property"] = {'sub': 1}
print my_dict.property.sub # 1 will be printed
my_dict = DictAsMember()
my_dict["property"] = [{'name': 1}, {'name': 2}]
print my_dict.property[1].name # 2 will be printed
Rather than writing your own class to implement the my_dict.property notation (this is called object notation) you could instead use named tuples. Named tuple can be referenced using object like variable deferencing or the standard tuple syntax. From the documentation
The [named tuple] is used to create tuple-like objects that have fields accessible
by attribute lookup as well as being indexable and iterable.
As an example of their use:
from collections import *
my_structure = namedtuple('my_structure', ['name', 'property'])
my_property = namedtuple('my_property', ['sub'])
s = my_structure('fred', my_property(1))
s # my_structure(name='fred', property=my_property(sub=1)) will be printed
s.name # 'fred' will be printed
s.property # my_property(sub=1) will be printed
s.property.sub # 1 will be printed
See also the accepted answer to this question for a nice summary of named tuples.

Categories