Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Count reads from python dictionary with unpacking

I am interested in counting the number of accesses to a dictionary's values. I am unsure how to include dictionary unpacking in the counter. Any tips?

from collections import defaultdict

class LDict(dict):
    def __init__(self, *args, **kwargs):
        '''
        This is a read-counting dictionary
        '''
        super().__init__(*args, **kwargs)
        self._lookup = defaultdict(lambda : 0)

    def __getitem__(self, key):
        retval = super().__getitem__(key)
        self._lookup[key] += 1
        return retval

    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        self._lookup[key] = self._lookup.default_factory()

    def __delitem__(self, key):
        super().__delitem__(self, key)
        _ = self._lookup[key]
        del self._lookup[key]

    def list_unused(self):
        return [key for key in self if self._lookup[key] == 0]

l = LDict(a='apple', b='bugger')

print({**l, **l})
print(l.list_unused())
_ = l['a']
print(l.list_unused())
like image 331
probinso Avatar asked Jul 08 '26 22:07

probinso


1 Answers

You need to override more methods. Access is not centralized through __getitem__(): other methods like copy(), items(), etc. access the keys without going through __getitem()__. I would assume the ** operator uses items(), but you will need to handle ALL of the methods to keep track of EVERY access. In many cases you will have to make a judgement call. For example, does __repr__() count as an access? The returned string contains every key and value formatted, so I think it does.

I would recommend overriding all of these methods, because you have to do bookkeeping on assignment too.

def __repr__(self):
def __len__(self):
def __iter__(self):
def clear(self):
def copy(self):
def has_key(self, k):
def update(self, *args, **kwargs):
def keys(self):
def values(self):
def items(self):

EDIT: So apparently there's an important caveat here that directly relates to your implementation. if LDict extends dict, then none of these methods are invoked during the dictionary unpacking { **l, **l}.

Apparently you can follow the advice here though, and implement LDict without extending dict. This worked for me:

from collections import MutableMapping

class LDict(MutableMapping):
    def __init__(self, *args, **kwargs):
        '''
        This is a read-counting dictionary
        '''
        self._lookup = defaultdict(lambda : 0)
        self.data = {}
        if kwargs:
            self.data.update(kwargs)

    def __getitem__(self, key):
        retval = self.data[key]
        self._lookup[key] += 1
        return retval

    def __setitem__(self, key, value):
        self.data[key] = value
        self._lookup[key] = self._lookup.default_factory()

    def __delitem__(self, key):
        del self.data[key]
        _ = self._lookup[key]
        del self._lookup[key]

    def items(self):
        print('items is being called!')
        yield from self.data.items()

    def __iter__(self):
        print('__iter__ is being called!')
        yield from self.data

    def __len__(self):
        return len(self.data)    


    def list_unused(self):
        return [key for key in self if self._lookup[key] == 0]

l = LDict(a='apple', b='bugger')

print({**l, **l})
print(l.list_unused())
_ = l['a']
print(l.list_unused())

which produces the output:

__iter__ is being called!
__iter__ is being called!
{'b': 'bugger', 'a': 'apple'}
__iter__ is being called!
[]
__iter__ is being called!
[]

(I only implemented the bare minimum to get example to work, I still recommend implementing the set of methods I listed about if you want your counts to be correct!)

So I guess the answer to your question is you have to

  1. Implement the __iter__(self) method
  2. DO NOT inherit from dict().
like image 86
olooney Avatar answered Jul 11 '26 11:07

olooney



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!