I am currently using a defaultdict
of Counter
to uniquely count several unpredictable values for unpredictable keys:
from collections import defaultdict, Counter
d = defaultdict(Counter)
d['x']['b'] += 1
d['x']['c'] += 1
print(d)
This gives me the expected result:
defaultdict(<class 'collections.Counter'>, {'x': Counter({'c': 1, 'b': 1})})
I now need to expand the structure of the values in the defaultdict
and make it a dict
with two keys: the previous Counter
and an str
:
mystruct = {
'counter': collections.Counter(),
'name': ''
}
Is it possible to use a specific data structure (like the above) as the default_factory
in defaultdict
? The expected result would be that for each nonexistent key in the defaultdict
, a new key and value initialized with the structure above would be created.
Defaultdict is a sub-class of the dictionary class that returns a dictionary-like object. The functionality of both dictionaries and defaultdict are almost same except for the fact that defaultdict never raises a KeyError. It provides a default value for the key that does not exists.
A defaultdict can be created by giving its declaration an argument that can have three values; list, set or int. According to the specified data type, the dictionary is created and when any key, that does not exist in the defaultdict is added or accessed, it is assigned a default value as opposed to giving a KeyError .
The Python defaultdict type behaves almost exactly like a regular Python dictionary, but if you try to access or modify a missing key, then defaultdict will automatically create the key and generate a default value for it. This makes defaultdict a valuable option for handling missing keys in dictionaries.
You just need to define your default_factory as a function that returns the dictionary you want to default to:
from collections import defaultdict, Counter
d = defaultdict(lambda: {'counter': Counter(), 'name': ''})
d['x']['counter']['b'] += 1
d['x']['counter']['c'] += 1
print(d)
If you are not familiar with lambdas, this is the same thing as:
def my_factory():
aDict = {'counter': Counter(), 'name':''}
return aDict
d = defaultdict(my_factory)
An alternate solution to drootang's answer is to use a custom class:
from collections import defaultdict, Counter
class NamedCounter:
def __init__(self, name = '', counter = None):
self.counter = counter if counter else Counter()
self.name = name
def __repr__(self):
return 'NamedCounter(name={}, counter={})'.format(
repr(self.name), repr(self.counter))
d = defaultdict(NamedCounter)
d['x'].counter['b'] += 1
d['x'].counter['b'] += 1
d['x'].name = 'X counter'
print(d)
defaultdict(<class __main__.NamedCounter at 0x19de808>, {'x': NamedCounter(name='X counter', counter=Counter({'b': 2}))})
Alternatively, you can extend Counter
to incorporate the name into the counter itself:
from collections import defaultdict, Counter
class NamedCounter(Counter):
def __init__(self, name = '', dict = None):
super(Counter, self).__init__(dict if dict else {})
self.name = name
def __repr__(self):
return 'NamedCounter(name={}, dict={})'.format(
repr(self.name), super(Counter, self).__repr__())
d = defaultdict(NamedCounter)
d['x']['b'] += 1
d['x']['b'] += 1
d['x'].name = 'X counter'
print(d)
defaultdict(<class '__main__.NamedCounter'>, {'x': NamedCounter(name='X counter', dict={'b': 2})})
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