6

As the many questions on the topic here on SO attest, taking a slice of a dictionary is a pretty common task, with a fairly nice solution:

{k:v for k,v in dict.viewitems() if some_test(k,v)}

But that creates a new dictionary, with its own mappings. For many operations, it would be nice to just have an immutable view of the original dict (i.e. it does not support assignment or deletion operations on the view). Implementing such a type is probably easy, but it's not good to have a proliferation of local utility classes.

So, my question is: is there a built-in way of obtaining such a "subset view"? Or is there a third-party library (preferably available via PyPi) that provides a good implementation of such a utility?

Marcin
  • 48,559
  • 18
  • 128
  • 201
  • 1
    I would suggest that an "immutable view" on a dictionary is exactly what you get with your example code...because absent making a separate copy of the dictionary, I'm not sure how you're going to make the "immutable" part work. – larsks Feb 17 '12 at 14:07
  • @larsks: It could simply not support assignment. – Marcin Feb 17 '12 at 14:16
  • 1
    @larsks: I assume that by "immutable view" the OP means that the view object itself has no methods to mutate the dictionary (eg pop), and that any changes to the wrapped dict are immediately visible in the view. Of course, it's not immutable in a "deep" sense -- i.e., if you do my_view[some_key].append(12), then of course the value corresponding to 12 will be modified. – Edward Loper Feb 17 '12 at 14:17

3 Answers3

7

There seems to be no builtin way to obtain a view into a dictionary. The easiest workaround appears to be Jochen's approach. I adapted his code slightly to make it work for my purposes:

from collections import MutableMapping

class DictView(MutableMapping):
    def __init__(self, source, valid_keys):
        self.source, self.valid_keys = source, valid_keys

    def __getitem__(self, key):
        if key in self.valid_keys:
            return self.source[key]
        else:
            raise KeyError(key)

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

    def __iter__(self):
        for key in self.valid_keys:
            yield key

    def __setitem__(self, key, value):
        if key in self.valid_keys:
            self.source[key] = value
        else:
            raise KeyError(key)

    def __delitem__(self, key):
        self.valid_keys.remove(key)

d = dict(a=1, b=2, c=3)
valid_keys = ['a', 'c']
d2 = DictView(d, valid_keys)
d2['a'] = -1  # overwrite element 'a' in source dictionary
print d  # prints {'a': -1, 'c': 3, 'b': 2}

So d2 behaves like a dictionary in all aspects except for printing, due to the different __repr__() method. Inheriting from dict to get __repr__() would require reimplementation of each and every method, as is done for collections.OrderedDict. If one wants only a readonly view, one can inherit from collections.Mapping and save the implementation of __setitem__() and __delitem__(). I find DictView useful to select parameters from self.__dict__ and pass them on in a compact form.

Stefan
  • 4,380
  • 2
  • 30
  • 33
4

This is pretty easy to implement:

from collections import Mapping
class FilteredItems(Mapping):
    def __init__(self, source, filter):
        self.source = source
        self.p = filter

    def __getitem__(self, key):
        x = self.source[key]
        if self.p(key,x):
            return key,x
        else:
            raise KeyError(key)


d2 = FilteredItems(d, some_test)
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • 2
    Yes, this is pretty easy to implement, and this looks good, but as noted, lots of local implementations and names are not a great thing. – Marcin Feb 17 '12 at 14:58
2

To clarify the semantics, you're thinking of something like this:?

class FilteredDictView:
    def __init__(self, base_dict, test):
        self._base_dict = base_dict
        self._test = test
    def __getitem__(self, key):
        value = self._base_dict[key] # might throw KeyError
        if not self._test(key,value):
            throw KeyError(key)
        return value
    # ... implement remaining dict-like-methods ...

If so, then I don't know of any such third party class. If you want to make implementing the remaining methods a little easier, you might look at using "UserDict" as a base class, which is basically just a wrapper for dict (the "UserDict.data" attribute is used to store the wrapped dict).

Edward Loper
  • 15,374
  • 7
  • 43
  • 52