7

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.

poke
  • 369,085
  • 72
  • 557
  • 602
WoJ
  • 27,165
  • 48
  • 180
  • 345

2 Answers2

10

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)
drootang
  • 2,351
  • 1
  • 20
  • 30
2

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})})

Raniz
  • 10,882
  • 1
  • 32
  • 64