Creating a dict-like class that counts successful and failed key matches

Chris Angelico rosuav at gmail.com
Mon Jun 30 10:28:56 EDT 2014


On Mon, Jun 30, 2014 at 11:43 PM,  <python at bdurham.com> wrote:
> As a diagnostic tool, I would like to create a dict-like class that counts
> successful and failed key matches by key. By failed I mean in the sense that
> a default value was returned vs. an exception raised. By count, I mean by
> tracking counts for individual keys vs. just total success/failure counts.
> The class needs to support setting values, retrieving values, and retrieving
> keys, items, and key/item pairs. Basically anything that a regular dict, I'd
> like my modified class to do as well.

Sounds like you want to subclass dict, then. Something like this:

class StatsDict(dict):
    def __init__(self, *a, **ka):
        super().__init__(*a, **ka)
        self.success = defaultdict(int)
        self.fail = defaultdict(int)
    def __getitem__(self, item):
        try:
            ret = super().__getitem__(item)
            self.success[item] += 1
            return ret
        except KeyError:
            self.fail[item] += 1
            raise

On initialization, set up some places for keeping track of stats. On
item retrieval (I presume you're not also looking for stats on item
assignment - for that, you'd want to also override __setitem__),
increment either the success marker or the fail marker for that key,
based exactly on what you say: was something returned, or was an
exception raised.

To get the stats, just look at the success and fail members:

>>> d = StatsDict()
>>> d["foo"]=1234
>>> d["foo"]
1234
>>> d["spam"]
(chomp)
KeyError: 'spam'
>>> d["foo"]
1234
>>> d["foo"]
1234
>>> d["test"]
(chomp)
KeyError: 'test'
>>> len(d.success) # Unique successful keys
1
>>> len(d.fail) # Unique failed keys
2
>>> sum(d.success.values()) # Total successful lookups
3
>>> sum(d.fail.values()) # Total unsuccessful lookups
2

You can also interrogate the actual defaultdicts, eg to find the hottest N keys.

For everything other than simple key retrieval, this should function
identically to a regular dict. Its repr will be a dict's repr, its
iteration will be its keys, all its other methods will be available
and won't be affected by this change. Notably, the .get() method isn't
logged; if you use that and want to get stats for it, you'll have to
reimplement it - something like this:

    def get(self, k, d=None):
        try:
            return self[k]
        except KeyError:
            return d

The lookup self[k] handles the statisticking, but if you let this go
through to the dict implementation of get(), it seems to ignore
__getitem__.

This probably isn't exactly what you want, but it's a start, at least,
and something to tweak into submission :)

ChrisA



More information about the Python-list mailing list