[Python-ideas] Dict with inverse form

Ian Kelly ian.g.kelly at gmail.com
Fri Feb 12 15:50:40 EST 2016


On Fri, Feb 12, 2016 at 1:36 PM, Chris Barker <chris.barker at noaa.gov> wrote:
> On Fri, Feb 12, 2016 at 12:27 PM, Andrew Barnert via Python-ideas
> <python-ideas at python.org> wrote:
>>
>> I look forward to it. Next time I need some variation of this, even if it
>> *isn't* the same variation you end up building, the fact that there's a
>> potential de facto standard for what to call the ".inv" or whatever still
>> helps me, right?
>
>
> yeah, though I'm not sure I like that name... (can't think of a better one
> right now, though).
>
> But what I would like is for the "inverse" to be available as an object
> itself, so:
>
> my_double_dict = DoubleDict( ( (1:'a'), (2:'b'), (3:'c) ) )
> my_inverse = my_double_dict.inv
>
> my_double_dict[1] == 'a'
> my_inverse['a'] == 1
>
>
> i.e you now have two objects, which are essentially the same object, but
> with inverse referencing semantics.

I have some unpublished (lightly tested) code that basically does this
(inspired by Guava's BiMap).


class BiDict(dict):

    def __init__(self, *args, **kwargs):
        self._inverse = _BiDictInverse(self)
        self._super_inverse = super(BiDict, self._inverse)
        self.update(*args, **kwargs)

    @property
    def inverse(self):
        return self._inverse

    def __repr__(self):
        return 'BiDict(%s)' % super().__repr__()

    def __setitem__(self, key, value):
        if value in self._inverse and self._inverse[value] != key:
            raise ValueError(
                '%r already bound to %r' % (value, self._inverse[value]))
        if key in self:
            self._super_inverse.__delitem__(self[key])
        super().__setitem__(key, value)
        self._super_inverse.__setitem__(value, key)

    def __delitem__(self, key):
        self._super_inverse.__delitem__(self[key])
        super().__delitem__(key)

    def clear(self):
        super().clear()
        self._super_inverse.clear()

    def pop(self, key, *args):
        key_was_present = key in self
        value = super().pop(key, *args)
        if key_was_present:
            self._super_inverse.__delitem__(value)
        return value

    def popitem(self):
        (key, value) = super().popitem()
        self._super_inverse.__delitem__(value)
        return (key, value)

    def setdefault(self, key, *args):
        key_was_present = key in self
        value = super().setdefault(key, *args)
        if not key_was_present:
            self._super_inverse.__setitem__(value, key)
        return value

    def update(self, *args, **kwargs):
        if len(args) > 1:
            raise TypeError(
                'update expected at most 1 arguments, got %d' % len(args))
        if args and hasattr(args[0], 'keys'):
            for key in args[0]:
                self[key] = args[0][key]
        elif args:
            for key, value in args[0]:
                self[key] = value
        for key in kwargs:
            self[key] = kwargs[key]


class _BiDictInverse(BiDict):

    def __init__(self, forward_dict):
        self._inverse = forward_dict
        self._super_inverse = super(BiDict, self._inverse)


More information about the Python-ideas mailing list