If that's your use case, there's an even easier interface: just toss the forward and reverse mappings in the same dict. If you don't need to iterate the dict[^1] or print in user-friendly form, it's hard to beat that for simplicity or compactness.[^2]
Of the top of my head (untested):
class SelfInvertingDict(dict):
def __delitem__(self, key):
super().__delitem__(self[key])
super().__delitem__(key)
def __setitem__(self, key, value):
if value in self and self[value] != key:
raise ValueError("duplicate key: '{}'".format(value)
if key in self: del self[key]
super().__setitem__(key, value)
super().__setitem__(value, key)
[^1]: Even if you do need to iterate, you can always do "(or don't mind iterating with a type switch like "names = (key for key in d if isinstance(key, str))"
[^2]: On the other hand, it's pretty easy to beat for static-type-checking purposes. The actual type of that dual dict is pretty ugly, and probably not describable in mypy terms, so presumably you'd just punt and label it Dict[Any, Any], or at best stick Union types in for key and value. But that's true for the double-dict-with-fall-through design too.