Introduction#

The bidict library provides several friendly, efficient data structures for working with bidirectional mappings in Python.

bidict.bidict#

bidict.bidict is the main bidirectional mapping data structure provided. It allows looking up the value associated with a key, just like a dict:

>>> element_by_symbol = bidict({'H': 'hydrogen'})
>>> element_by_symbol['H']
'hydrogen'

But it also allows looking up the key associated with a value, via the special inverse attribute:

>>> element_by_symbol.inverse['hydrogen']
'H'

The inverse attribute actually references the entire inverse bidirectional mapping:

>>> element_by_symbol
bidict({'H': 'hydrogen'})
>>> element_by_symbol.inverse
bidict({'hydrogen': 'H'})

…which is automatically kept in sync as the original mapping is updated:

>>> element_by_symbol['H'] = 'hydrogène'
>>> element_by_symbol.inverse
bidict({'hydrogène': 'H'})

If you’re used to working with dicts, you’ll feel right at home using bidict:

>>> dir(element_by_symbol)
[..., '__getitem__', ..., '__setitem__', ..., 'items', 'keys', ...]

Familiar, concise, Pythonic.

Why can’t I just use a dict?#

A skeptic writes:

If I want a mapping associating a → b and b → a, I can just create the dict {a: b, b: a}. Why bother using bidict?

One answer is better ergonomics for maintaining a correct representation. For example, to get the correct length, you’d have to take the number reported by len() and cut it in half.

But now consider what happens when we need to store a new association, and we try to do so naively:

>>> el_by_sym = {'H': 'hydrogen', 'hydrogen': 'H'}
>>> # Later we need to associate 'H' with a different value
>>> el_by_sym.update({'H': 'hydrogène', 'hydrogène': 'H'}  # Too naive

Here is what we’re left with:

>>> el_by_sym
{'H': 'hydrogène', 'hydrogène': 'H', 'hydrogen': 'H'}

Oops.

We forgot to look up whether the key and value we wanted to set already had any previous associations and remove them as necessary.

In general, if we want to store the association k ⟷ v, but we may have already stored the associations k ⟷ v′ or k′ ⟷ v, a correct implementation using the single-dict approach would require code like this:

>>> d = {'H': 'hydrogen', 'hydrogen': 'H'}

>>> def update(d, key, val):
...     oldval = d.pop(key, object())
...     d.pop(oldval, None)
...     oldkey = d.pop(val, object())
...     d.pop(oldkey, None)
...     d.update({key: val, val: key})

>>> update(d, 'H', 'hydrogène')
>>> d == {'H': 'hydrogène', 'hydrogène': 'H'}
True

With bidict, we can instead just write:

>>> b = bidict({'H': 'hydrogen'})
>>> b['H'] = 'hydrogène'

And bidict takes care of all the fussy details, leaving us with just what we wanted:

>>> b
bidict({'H': 'hydrogène'})

>>> b.inverse
bidict({'hydrogène': 'H'})

Even more important…#

Beyond this, consider what would happen if we needed to work with just the keys, values, or items that we have associated.

Since the single-dict approach inserts values as keys into the same dict that it inserts keys into, we’d never be able to tell our keys and values apart.

So iterating over the keys would also yield the values (and vice versa), with no way to tell which was which.

Iterating over the items would yield twice as many as we wanted, with a (v, k) item that we’d have to ignore for each (k, v) item that we expect, and no way to tell which was which.

>>> # Compare the single-dict approach:
>>> set(d.keys()) == {'H', 'hydrogène'}  # .keys() also gives values
True
>>> set(d.values()) == {'H', 'hydrogène'}  # .values() also gives keys
True

>>> # ...to using a bidict:
>>> b.keys() == {'H'}  # just the keys
True
>>> b.values() == {'hydrogène'}  # just the values
True

In short, to model a bidirectional mapping correctly and unambiguously, we need two separate one-directional mappings, one for the forward associations and one for the inverse, that are kept in sync as the associations change.

This is exactly what bidict does under the hood, abstracting it into a clean and ergonomic interface.

bidict’s APIs also provide power, flexibility, and safety, making sure the one-to-one invariant is maintained and inverse mappings are kept consistent, while also helping make sure you don’t accidentally shoot yourself in the foot.

Additional Functionality#

Besides the standard bidict.bidict type, the bidict module provides other bidirectional mapping variants: frozenbidict, OrderedBidict, FrozenOrderedBidict, and namedbidict().

These, and bidict’s other functionality, will be covered in later sections.

But first, let’s look at a few more details of Basic Usage.

Basic Usage#

Let’s return to the example from the Introduction:

>>> element_by_symbol = bidict(H='hydrogen')

As we saw, this behaves just like a dict, but maintains a special inverse attribute giving access to inverse items:

>>> element_by_symbol.inverse['helium'] = 'He'
>>> del element_by_symbol.inverse['hydrogen']
>>> element_by_symbol
bidict({'He': 'helium'})

bidict.bidict supports the rest of the collections.abc.MutableMapping interface as well:

>>> 'C' in element_by_symbol
False
>>> element_by_symbol.get('C', 'carbon')
'carbon'
>>> element_by_symbol.pop('He')
'helium'
>>> element_by_symbol
bidict()
>>> element_by_symbol.update(Hg='mercury')
>>> element_by_symbol
bidict({'Hg': 'mercury'})
>>> 'mercury' in element_by_symbol.inverse
True
>>> element_by_symbol.inverse.pop('mercury')
'Hg'

Because inverse items are maintained alongside forward items, referencing a bidict’s inverse is always a constant-time operation.

Values Must Be Hashable#

Because you must be able to look up keys by value as well as values by key, values must also be hashable.

Attempting to insert an unhashable value will result in an error:

>>> anagrams_by_alphagram = dict(opt=['opt', 'pot', 'top'])
>>> bidict(anagrams_by_alphagram)
Traceback (most recent call last):
...
TypeError: ...

So in this example, using a tuple or a frozenset instead of a list would do the trick:

>>> bidict(opt=('opt', 'pot', 'top'))
bidict({'opt': ('opt', 'pot', 'top')})

Values Must Be Unique#

As we know, in a bidirectional map, not only must keys be unique, but values must be unique as well. This has immediate implications for bidict’s API.

Consider the following:

>>> b = bidict({'one': 1})
>>> b['two'] = 1  

What should happen next?

If the bidict allowed this to succeed, because of the uniqueness-of-values constraint, it would silently clobber the existing item, resulting in:

>>> b  
bidict({'two': 1})

This could result in surprises or problems down the line.

Instead, bidict raises a ValueDuplicationError so you have an opportunity to catch this early and resolve the conflict before it causes problems later on:

>>> b['two'] = 1
Traceback (most recent call last):
    ...
ValueDuplicationError: 1

The purpose of this is to be more in line with the Zen of Python, which advises,

Errors should never pass silently.
Unless explicitly silenced.

So if you really just want to clobber any existing items, all you have to do is say so explicitly:

>>> b.forceput('two', 1)
>>> b
bidict({'two': 1})

Similarly, initializations and update() calls that would overwrite the key of an existing value raise an exception too:

>>> bidict({'one': 1, 'uno': 1})
Traceback (most recent call last):
    ...
ValueDuplicationError: 1

>>> b = bidict({'one': 1})
>>> b.update([('uno', 1)])
Traceback (most recent call last):
    ...
ValueDuplicationError: 1

>>> b
bidict({'one': 1})

Setting an existing key to a new value does not cause an error, and is considered an intentional overwrite of the value associated with the existing key, in keeping with dict’s behavior:

>>> b = bidict({'one': 1})
>>> b['one'] = 2  # succeeds
>>> b
bidict({'one': 2})
>>> b.update([('one', 3), ('one', 4), ('one', 5)])
>>> b
bidict({'one': 5})
>>> bidict([('one', 1), ('one', 2)])
bidict({'one': 2})

In summary, when attempting to insert an item whose key duplicates an existing item’s, bidict’s default behavior is to allow the insertion, overwriting the existing item with the new one. When attempting to insert an item whose value duplicates an existing item’s, bidict’s default behavior is to raise. This design naturally falls out of the behavior of Python’s built-in dict, and protects against unexpected data loss.

One set of alternatives to this behavior is provided by forceput() (mentioned above) and forceupdate(), which allow you to explicitly overwrite existing keys and values:

>>> b = bidict({'one': 1})
>>> b.forceput('two', 1)
>>> b
bidict({'two': 1})

>>> b.forceupdate([('three', 1), ('four', 1)])
>>> b
bidict({'four': 1})

For even more control, you can use put() and putall(). These variants allow you to pass an OnDup instance to specify custom OnDupActions for each type of duplication that can occur.

>>> from bidict import OnDup, RAISE

>>> b = bidict({1: 'one'})
>>> b.put(1, 'uno', OnDup(key=RAISE))
Traceback (most recent call last):
    ...
KeyDuplicationError: 2
>>> b
bidict({1: 'one'})

bidict provides the ON_DUP_DEFAULT, ON_DUP_RAISE, and ON_DUP_DROP_OLD OnDup instances for convenience.

If no on_dup argument is passed, put() and putall() will use ON_DUP_RAISE, providing stricter-by-default alternatives to __setitem__() and update(). (These defaults complement the looser alternatives provided by forceput() and forceupdate().)

Key and Value Duplication#

Note that it’s possible for a given item to duplicate the key of one existing item, and the value of another existing item. In the following example, the key of the third item duplicates the first item’s key, and the value of the third item dulicates the second item’s value:

>>> b.putall([(1, 2), (3, 4), (1, 4)], OnDup(key=...))

What should happen next?

Keep in mind, the active OnDup may specify one OnDupAction for key duplication and a different OnDupAction for value duplication.

To account for this, OnDup allows you to use its kv field to indicate how you want to handle this case without ambiguity:

>>> from bidict import DROP_OLD
>>> on_dup = OnDup(key=DROP_OLD, val=RAISE, kv=RAISE)
>>> b.putall([(1, 2), (3, 4), (1, 4)], on_dup)
Traceback (most recent call last):
    ...
KeyAndValueDuplicationError: (1, 4)

If not specified, kv defaults to whatever was provided for val.

Note that repeated insertions of the same item are construed as a no-op and will not raise, no matter what the active OnDup is:

>>> b = bidict({1: 'one'})
>>> b.put(1, 'one')  # no-op, not a DuplicationError
>>> b.putall([(2, 'two'), (2, 'two')])  # The repeat (2, 'two') is also a no-op.
>>> sorted(b.items())
[(1, 'one'), (2, 'two')]

See the YoloBidict Recipe for another way to customize this behavior.

Updates Fail Clean#

If an update to a bidict fails, you can be sure that it fails clean. In other words, a bidict will never apply only part of an update that ultimately fails, without restoring itself to the state it was in before processing the update:

>>> b = bidict({1: 'one', 2: 'two'})
>>> b.putall([(3, 'three'), (1, 'uno')])
Traceback (most recent call last):
    ...
KeyDuplicationError: 1

>>> # (1, 'uno') was the problem...
>>> b  # ...but (3, 'three') was not added either:
bidict({1: 'one', 2: 'two'})

Order Matters#

Performing a bulk insert operation – i.e. passing multiple items to __init__(), update(), forceupdate(), or putall() – is like inserting each of those items individually in sequence. 1

Therefore, the order of the items provided to the bulk insert operation is significant to the result:

>>> b = bidict({0: 0, 1: 2})
>>> b.forceupdate([(2, 0), (0, 1), (0, 0)])

>>> # 1. (2, 0) overwrites (0, 0)             -> bidict({2: 0, 1: 2})
>>> # 2. (0, 1) is added                      -> bidict({2: 0, 1: 2, 0: 1})
>>> # 3. (0, 0) overwrites (0, 1) and (2, 0)  -> bidict({0: 0, 1: 2})

>>> sorted(b.items())
[(0, 0), (1, 2)]

>>> b = bidict({0: 0, 1: 2})  # as before
>>> # Give the same items to forceupdate() but in a different order:
>>> b.forceupdate([(0, 1), (0, 0), (2, 0)])

>>> # 1. (0, 1) overwrites (0, 0)             -> bidict({0: 1, 1: 2})
>>> # 2. (0, 0) overwrites (0, 1)             -> bidict({0: 0, 1: 2})
>>> # 3. (2, 0) overwrites (0, 0)             -> bidict({1: 2, 2: 0})

>>> sorted(b.items())  # different items!
[(1, 2), (2, 0)]
1

Albeit with the extremely important advantage of failing clean.

Interop#

bidicts interoperate well with other types of mappings. For example, they support (efficient) polymorphic equality testing:

>>> bidict(a=1) == dict(a=1)
True

And converting back and forth works as expected (assuming no value duplication):

>>> dict(bidict(a=1))
{'a': 1}
>>> bidict(dict(a=1))
bidict({'a': 1})

See the Polymorphism section for more interoperability documentation.


Hopefully bidict feels right at home among the Python built-ins you already know. Proceed to Other bidict Types for documentation on the remaining bidict variants.

Other bidict Types#

Now that we’ve covered Basic Usage with the bidict.bidict type, let’s look at some other bidirectional mapping types.

Bidict Types Diagram#

bidict types diagram

All bidirectional mapping types that bidict provides are subclasses of bidict.BidirectionalMapping. This abstract base class extends collections.abc.Mapping by adding the “inverseabstractproperty.

As you may have noticed, bidict.bidict is also a collections.abc.MutableMapping. But bidict provides immutable bidirectional mapping types as well.

frozenbidict#

frozenbidict is an immutable, hashable bidirectional mapping type.

As you would expect, attempting to mutate a frozenbidict causes an error:

>>> from bidict import frozenbidict
>>> f = frozenbidict({'H': 'hydrogen'})
>>> f['C'] = 'carbon'
Traceback (most recent call last):
    ...
TypeError: 'frozenbidict' object does not support item assignment

frozenbidict also implements collections.abc.Hashable, so it’s suitable for insertion into sets or other mappings:

>>> my_set = {f}      # not an error
>>> my_dict = {f: 1}  # also not an error

See the frozenbidict API documentation for more information.

OrderedBidict#

bidict.OrderedBidict is a MutableBidirectionalMapping that preserves the ordering of its items, and offers some additional ordering-related APIs that unordered bidicts can’t offer. It’s like a bidirectional version of collections.OrderedDict.

>>> from bidict import OrderedBidict
>>> element_by_symbol = OrderedBidict([
...     ('H', 'hydrogen'), ('He', 'helium'), ('Li', 'lithium')])

>>> element_by_symbol.inverse
OrderedBidict([('hydrogen', 'H'), ('helium', 'He'), ('lithium', 'Li')])

>>> first, second, third = element_by_symbol.values()
>>> first, second, third
('hydrogen', 'helium', 'lithium')

>>> # Insert an additional item and verify it now comes last:
>>> element_by_symbol['Be'] = 'beryllium'
>>> last_item = list(element_by_symbol.items())[-1]
>>> last_item
('Be', 'beryllium')

Additional, efficiently-implemented, order-mutating APIs modeled after OrderedDict, e.g. popitem(last: bool), which makes ordered bidicts suitable for use as FIFO queues, and move_to_end(last: bool), are provided as well:

>>> element_by_symbol.popitem(last=True)   # Remove the last item
('Be', 'beryllium')
>>> element_by_symbol.popitem(last=False)  # Remove the first item
('H', 'hydrogen')

>>> # Re-adding hydrogen after it's been removed moves it to the end:
>>> element_by_symbol['H'] = 'hydrogen'
>>> element_by_symbol
OrderedBidict([('He', 'helium'), ('Li', 'lithium'), ('H', 'hydrogen')])

>>> # But there's also a `move_to_end` method just for this purpose:
>>> element_by_symbol.move_to_end('Li')
>>> element_by_symbol
OrderedBidict([('He', 'helium'), ('H', 'hydrogen'), ('Li', 'lithium')])

>>> element_by_symbol.move_to_end('H', last=False)  # move to front
>>> element_by_symbol
OrderedBidict([('H', 'hydrogen'), ('He', 'helium'), ('Li', 'lithium')])

As with OrderedDict, updating an existing item preserves its position in the order:

>>> element_by_symbol['He'] = 'updated in place!'
>>> element_by_symbol
OrderedBidict([('H', 'hydrogen'), ('He', 'updated in place!'), ('Li', 'lithium')])

Collapsing overwrites#

When setting an item in an ordered bidict whose key duplicates that of an existing item, and whose value duplicates that of a different existing item, the existing item whose value is duplicated will be dropped, and the existing item whose key is duplicated will have its value overwritten in place:

>>> o = OrderedBidict([(1, 2), (3, 4), (5, 6), (7, 8)])
>>> o.forceput(3, 8)  # item with duplicated value (7, 8) is dropped...
>>> o  # and the item with duplicated key (3, 4) is updated in place:
OrderedBidict([(1, 2), (3, 8), (5, 6)])
>>> # (3, 8) took the place of (3, 4), not (7, 8)

>>> o = OrderedBidict([(1, 2), (3, 4), (5, 6), (7, 8)])  # as before
>>> o.forceput(5, 2)  # another example
>>> o
OrderedBidict([(3, 4), (5, 2), (7, 8)])
>>> # (5, 2) took the place of (5, 6), not (1, 2)

__eq__() is order-insensitive#

To ensure that equals comparison for any bidict always upholds the transitive property of equality and the Liskov substitution principle, equality tests between a bidict and another mapping are always order-insensitive, even for ordered bidicts:

>>> o1 = OrderedBidict({1: 1, 2: 2})
>>> o2 = OrderedBidict({2: 2, 1: 1})
>>> o1 == o2
True

For order-sensitive equality tests, use equals_order_sensitive():

>>> o1.equals_order_sensitive(o2)
False

(Note that this differs from the behavior of collections.OrderedDict.__eq__(), and for good reason, by recommendation of the Python core developer who designed and implemented OrderedDict. For more about this, see Python surprises.)

What about order-preserving dicts?#

In CPython 3.6+ and all versions of PyPy, dict (which bidicts are built on by default) preserves insertion order. Given that, can you get away with using an unordered bidict in places where you need an order-preserving bidirectional mapping? Of course, this assumes you don’t need the additional APIs offered only by OrderedBidict, such as popitem(last=False), which makes it suitable for use as a FIFO queue.

Consider this example:

>>> b = bidict({1: -1, 2: -2, 3: -3})
>>> b[2] = 'UPDATED'
>>> b
bidict({1: -1, 2: 'UPDATED', 3: -3})

So far so good, but look what happens here:

>>> b.inverse
bidict({-1: 1, -3: 3, 'UPDATED': 2})

The ordering of items between the bidict and its inverse instance is no longer consistent.

To ensure that ordering is kept consistent between a bidict and its inverse, no matter how it’s mutated, you have to use an ordered bidict:

>>> ob = OrderedBidict({1: -1, 2: -2, 3: -3})
>>> ob[2] = 'UPDATED'
>>> ob
OrderedBidict([(1, -1), (2, 'UPDATED'), (3, -3)])
>>> ob.inverse
OrderedBidict([(-1, 1), ('UPDATED', 2), (-3, 3)])

The ordered bidict and its inverse always give you a consistent ordering.

That said, if you depend on preserving insertion order, an unordered bidict may be sufficient if:

  • you’ll never mutate it (in which case, use a frozenbidict), or:

  • you only mutate by removing and/or adding whole new items, never changing just the key or value of an existing item, or:

  • you only depend on the order in the forward bidict, and are only changing existing items in the forward direction (i.e. changing values by key, rather than changing keys by value).

On the other hand, if your code is actually depending on the order, using an explicitly-ordered bidict type makes for clearer code.

OrderedBidict also gives you additional, constant-time, order-mutating APIs, such as move_to_end(last: bool) and popitem(last: bool). These additional APIs expand the range of use cases where an OrderedBidict can be used. For example, popitem(last=False) allows using an OrderedBidict as a FIFO queue.

If you’re on Python <= 3.7, OrderedBidict also gives you __reversed__(), which you don’t get with unordered bidicts unless you upgrade to Python 3.8+.

FrozenOrderedBidict#

FrozenOrderedBidict is an immutable ordered bidict type. It’s like a hashable OrderedBidict without the mutating APIs, or like a reversible frozenbidict even on Python < 3.8. (All bidicts are order-preserving when never mutated, so frozenbidict is already order-preserving, but only on Python 3.8+, where dicts are reversible, are all bidicts (including frozenbidict) also reversible.)

If you are using Python 3.8+, frozenbidict gives you everything that FrozenOrderedBidict gives you, but with less space overhead.

namedbidict()#

bidict.namedbidict(), inspired by collections.namedtuple(), allows you to easily generate a new bidirectional mapping type with custom attribute-based access to forward and inverse mappings:

>>> from bidict import namedbidict
>>> ElementBySymbolBidict = namedbidict('ElementBySymbolBidict', 'symbol', 'name')
>>> el_by_sym = ElementBySymbolBidict(H='hydrogen', He='helium')
>>> el_by_sym.name_for['He']
'helium'
>>> el_by_sym.symbol_for['helium']
'He'
>>> el_by_sym.name_for['Ne'] = 'neon'
>>> el_by_sym
ElementBySymbolBidict({'H': 'hydrogen', 'He': 'helium', 'Ne': 'neon'})
>>> el_by_sym['H']  # regular lookup still works the same
'hydrogen'
>>> el_by_sym.inverse['hydrogen']  # and for the inverse as well
'H'
>>> el_by_sym.inverse
ElementBySymbolBidictInv({'hydrogen': 'H', 'helium': 'He', 'neon': 'Ne'})
>>> el_by_sym.inverse.name_for['H']  # custom attribute lookup works on the inverse too
'hydrogen'

Note

Notice how, unlike the other bidict types, namedbidict classes aren’t their own inverse classes, because the roles of the custom attribute-based accessors are inverted when accessing the inverse. BidictBase realizes when a subclass is not its own inverse, and dynamically generates the inverse class for you automatically. You can see this in action above if you look at the dynamically-generated inverse class name, ElementBySymbolBidictInv. For more about this, see Dynamic Inverse Class Generation.

Using the base_type keyword arg – whose default value is bidict.bidict – you can customize the bidict type used as the base class. For example, the following creates a named frozenbidict type:

>>> FrozenElBySymBidict = namedbidict('FrozenElBySymBidict', 'sym', 'name', base_type=frozenbidict)
>>> noble = FrozenElBySymBidict(He='helium', Ne='neon', Ar='argon', Kr='krypton')
>>> noble.sym_for['helium']
'He'
>>> hash(noble) is not TypeError  # does not raise TypeError: unhashable type
True
>>> noble['C'] = 'carbon'  # mutation fails - it's frozen!
Traceback (most recent call last):
...
TypeError: 'FrozenElBySymBidict' object does not support item assignment

Polymorphism#

Code that needs to check only whether an object is dict-like should not use isinstance(obj, dict). This check is too specific, because dict-like objects need not actually be instances of dict or a dict subclass. You can see this fails for many dict-like in the standard library:

>>> from collections import ChainMap
>>> chainmap = ChainMap()
>>> isinstance(chainmap, dict)
False

The same is true for all the bidict types:

>>> bi = bidict()
>>> isinstance(bi, dict)
False

A better way to check whether an object is dict-like is to use the Mapping abstract base class (ABC) from the collections.abc module, which provides a number of ABCs intended for this purpose:

>>> isinstance(chainmap, Mapping)
True
>>> isinstance(bi, Mapping)
True

Also note that the proper way to check whether an object is an (im)mutable mapping is to use the MutableMapping ABC:

>>> isinstance(chainmap, MutableMapping)
True
>>> isinstance(bi, MutableMapping)
True

You can combine this with bidict’s own BidirectionalMapping ABC to implement your own check for whether an object is an immutable, bidirectional mapping:

>>> def is_immutable_bimap(obj):
...     return (isinstance(obj, BidirectionalMapping)
...             and not isinstance(obj, MutableMapping))
>>> is_immutable_bimap(bidict())
False
>>> is_immutable_bimap(frozenbidict())
True

Using this in the next example, we can see the concept above in action again:

>>> fb = FrozenOrderedBidict()
>>> isinstance(fb, frozenbidict)
False
>>> is_immutable_bimap(fb)
True

Checking for isinstance(obj, frozenbidict) is too specific for this purpose and can fail in some cases. But using the collections ABCs as intended does the trick.

For more you can do with bidict, check out Extending bidict next.

Extending bidict#

Although bidict provides the various bidirectional mapping types covered already, it’s possible that some use case might require something more than what’s provided. For this reason, bidict was written with extensibility in mind.

Let’s look at some examples.

YoloBidict Recipe#

If you’d like ON_DUP_DROP_OLD to be the default on_dup behavior (for __init__(), __setitem__(), and update()), you can use the following recipe:

>>> from bidict import bidict, ON_DUP_DROP_OLD

>>> class YoloBidict(bidict):
...     on_dup = ON_DUP_DROP_OLD

>>> b = YoloBidict({'one': 1})
>>> b['two'] = 1  # succeeds, no ValueDuplicationError
>>> b
YoloBidict({'two': 1})

>>> b.update({'three': 1})  # ditto
>>> b
YoloBidict({'three': 1})

Of course, YoloBidict’s inherited put() and putall() methods still allow specifying a custom OnDup per call via the on_dup argument, and will both still default to raising for all duplication types.

Further demonstrating bidict’s extensibility, to make an OrderedYoloBidict, simply have the subclass above inherit from bidict.OrderedBidict rather than bidict.bidict.

Beware of ON_DUP_DROP_OLD#

There’s a good reason that bidict does not provide a YoloBidict out of the box.

Before you decide to use a YoloBidict in your own code, beware of the following potentially unexpected, dangerous behavior:

>>> b = YoloBidict({'one': 1, 'two': 2})  # contains two items
>>> b['one'] = 2                          # update one of the items
>>> b                                     # now only has one item!
YoloBidict({'one': 2})

As covered in Key and Value Duplication, setting an existing key to the value of a different existing item causes both existing items to quietly collapse into a single new item.

A safer example of this type of customization would be something like:

>>> from bidict import ON_DUP_RAISE

>>> class YodoBidict(bidict):  # Note, "Yodo" with a "d"
...     on_dup = ON_DUP_RAISE

>>> b = YodoBidict({'one': 1})
>>> b['one'] = 2  # Works with a regular bidict, but Yodo plays it safe.
Traceback (most recent call last):
    ...
bidict.KeyDuplicationError: one
>>> b
YodoBidict({'one': 1})
>>> b.forceput('one', 2)  # Any destructive change requires more force.
>>> b
YodoBidict({'one': 2})

WeakrefBidict Recipe#

Suppose you need a custom bidict type that only retains weakrefs to some objects whose refcounts you’re trying not increment.

With BidictBase's _fwdm_cls (forward mapping class) and _invm_cls (inverse mapping class) attributes, accomplishing this is as simple as:

>>> from bidict import MutableBidict
>>> from weakref import WeakKeyDictionary, WeakValueDictionary

>>> class WeakrefBidict(MutableBidict):
...     _fwdm_cls = WeakKeyDictionary
...     _invm_cls = WeakValueDictionary

Now you can insert items into WeakrefBidict without incrementing any refcounts:

>>> id_by_obj = WeakrefBidict()

>>> class MyObj:
...     def __init__(self, id):
...         self.id = id
...     def __repr__(self):
...         return f'<MyObj id={self.id}>'

>>> o1, o2 = MyObj(1), MyObj(2)
>>> id_by_obj[o1] = o1.id
>>> id_by_obj[o2] = o2.id
>>> id_by_obj
WeakrefBidict({<MyObj id=1>: 1, <MyObj id=2>: 2})
>>> id_by_obj.inverse
WeakrefBidictInv({1: <MyObj id=1>, 2: <MyObj id=2>})

If you drop your references to your objects, you can see that they get deallocated on CPython right away, since your WeakrefBidict isn’t holding on to them:

>>> del o1, o2
>>> id_by_obj
WeakrefBidict()

SortedBidict Recipes#

Suppose you need a bidict that maintains its items in sorted order. The Python standard library does not include any sorted dict types, but the excellent sortedcontainers and sortedcollections libraries do.

Armed with these, along with BidictBase’s _fwdm_cls (forward mapping class) and _invm_cls (inverse mapping class) attributes, creating a sorted bidict is simple:

>>> from sortedcontainers import SortedDict

>>> class SortedBidict(MutableBidict):
...     """A sorted bidict whose forward items stay sorted by their keys,
...     and whose inverse items stay sorted by *their* keys.
...     Note: As a result, an instance and its inverse yield their items
...     in different orders.
...     """
...     _fwdm_cls = SortedDict
...     _invm_cls = SortedDict
...     _repr_delegate = list  # only used for list-style repr

>>> b = SortedBidict({'Tokyo': 'Japan', 'Cairo': 'Egypt'})
>>> b
SortedBidict([('Cairo', 'Egypt'), ('Tokyo', 'Japan')])

>>> b['Lima'] = 'Peru'

>>> list(b.items())  # stays sorted by key
[('Cairo', 'Egypt'), ('Lima', 'Peru'), ('Tokyo', 'Japan')]

>>> list(b.inverse.items())  # .inverse stays sorted by *its* keys (b's values)
[('Egypt', 'Cairo'), ('Japan', 'Tokyo'), ('Peru', 'Lima')]

Here’s a recipe for a sorted bidict whose forward items stay sorted by their keys, and whose inverse items stay sorted by their values. i.e. An instance and its inverse will yield their items in the same order:

>>> from sortedcollections import ValueSortedDict

>>> class KeySortedBidict(MutableBidict):
...     _fwdm_cls = SortedDict
...     _invm_cls = ValueSortedDict
...     _repr_delegate = list

>>> elem_by_atomicnum = KeySortedBidict({
...     6: 'carbon', 1: 'hydrogen', 2: 'helium'})

>>> list(elem_by_atomicnum.items())  # stays sorted by key
[(1, 'hydrogen'), (2, 'helium'), (6, 'carbon')]

>>> list(elem_by_atomicnum.inverse.items())  # .inverse stays sorted by value
[('hydrogen', 1), ('helium', 2), ('carbon', 6)]

>>> elem_by_atomicnum[4] = 'beryllium'

>>> list(elem_by_atomicnum.inverse.items())
[('hydrogen', 1), ('helium', 2), ('beryllium', 4), ('carbon', 6)]

Automatic “Get Attribute” Pass-Through#

Python makes it easy to customize a class’s “get attribute” behavior. You can take advantage of this to pass attribute access through to the backing _fwdm mapping, for example, when an attribute is not provided by the bidict class itself:

>>> def __getattribute__(self, name):
...     try:
...         return object.__getattribute__(self, name)
...     except AttributeError:
...         return getattr(self._fwdm, name)
>>> KeySortedBidict.__getattribute__ = __getattribute__

Now, even though this KeySortedBidict itself provides no peekitem attribute, the following call still succeeds because it’s passed through to the backing SortedDict:

>>> elem_by_atomicnum.peekitem()
(6, 'carbon')

Dynamic Inverse Class Generation#

When a bidict class’s _fwdm_cls and _invm_cls are the same, the bidict class is its own inverse class. (This is the case for all the bidict classes that come with bidict.)

However, when a bidict’s _fwdm_cls and _invm_cls differ, as in the KeySortedBidict and WeakrefBidict recipes above, the inverse class of the bidict needs to have its _fwdm_cls and _invm_cls swapped.

BidictBase detects this and dynamically computes the correct inverse class for you automatically.

You can see this if you inspect KeySortedBidict’s inverse bidict:

>>> elem_by_atomicnum.inverse.__class__.__name__
'KeySortedBidictInv'

Notice that BidictBase automatically created a KeySortedBidictInv class and used it for the inverse bidict.

As expected, KeySortedBidictInv’s _fwdm_cls and _invm_cls are the opposite of KeySortedBidict’s:

>>> elem_by_atomicnum.inverse._fwdm_cls.__name__
'ValueSortedDict'
>>> elem_by_atomicnum.inverse._invm_cls.__name__
'SortedDict'

BidictBase also ensures that round trips work as expected:

>>> KeySortedBidictInv = elem_by_atomicnum.inverse.__class__  # i.e. a value-sorted bidict
>>> atomicnum_by_elem = KeySortedBidictInv(elem_by_atomicnum.inverse)
>>> atomicnum_by_elem
KeySortedBidictInv([('hydrogen', 1), ('helium', 2), ('beryllium', 4), ('carbon', 6)])
>>> KeySortedBidict(atomicnum_by_elem.inverse) == elem_by_atomicnum
True

This all goes to show how simple it can be to compose your own bidirectional mapping types out of the building blocks that bidict provides.

Other Functionality#

bidict.inverted()#

bidict provides the inverted iterator to help you get inverse items from various types of objects.

Pass in a mapping to get the inverse mapping:

>>> from bidict import inverted
>>> it = inverted({1: 'one'})
>>> {k: v for (k, v) in it}
{'one': 1}

…an iterable of pairs to get the pairs’ inverses:

>>> list(inverted([(1, 'one'), (2, 'two')]))
[('one', 1), ('two', 2)]
>>> list(inverted((i*i, i) for i in range(2, 5)))
[(2, 4), (3, 9), (4, 16)]

…or any object implementing an __inverted__ method, which objects that already know their own inverses (such as bidicts) can implement themselves:

>>> dict(inverted(bidict({1: 'one'})))
{'one': 1}
>>> list(inverted(OrderedBidict([(2, 4), (3, 9)])))
[(4, 2), (9, 3)]

Addendum#

Performance#

bidict strives to be as performant as possible while being faithful to its purpose. The need for speed is balanced with the responsibility to protect users from shooting themselves in the foot.

In general, accomplishing some task using bidict should have about the same performance as keeping two inverse dicts in sync manually. The test suite includes benchmarks for common workloads to catch any performance regressions.

If you spot a case where bidict’s performance could be improved, please don’t hesitate to file an issue or submit a pull request.

bidict Avoids Reference Cycles#

A careful reader might notice the following…

>>> fwd = bidict(one=1)
>>> inv = fwd.inverse
>>> inv.inverse is fwd
True

…and worry that a bidict and its inverse create a reference cycle. If this were true, in CPython this would mean that the memory for a bidict could not be immediately reclaimed when you retained no more references to it, but rather would have to wait for the next garbage collection to kick in before it could be reclaimed.

However, bidicts use a weakref.ref to store the inverse reference in one direction, avoiding the strong reference cycle. As a result, when you no longer retain any references to a bidict you create, you can be sure that its refcount in CPython drops to zero, and that its memory will therefore be reclaimed immediately.

Note

In PyPy this is not an issue, as PyPy doesn’t use reference counts. The memory for unreferenced objects in PyPy is only reclaimed when GC kicks in, which is unpredictable.

Terminology#

  • It’s intentional that the term “inverse” is used rather than “reverse”.

    Consider a collection of (k, v) pairs. Taking the reverse of the collection can only be done if it is ordered, and (as you’d expect) reverses the order of the pairs in the collection. But each original (k, v) pair remains in the resulting collection.

    By contrast, taking the inverse of such a collection neither requires the collection to be ordered nor guarantees any ordering in the result, but rather just replaces every (k, v) pair with the inverse pair (v, k).

  • “keys” and “values” could perhaps more properly be called “primary keys” and “secondary keys” (as in a database), or even “forward keys” and “inverse keys”, respectively. bidict sticks with the terms “keys” and “values” for the sake of familiarity and to avoid potential confusion, but technically values are also keys themselves.

    Concretely, this allows bidicts to return a set-like (dict_keys) object for values(), rather than a non-set-like dict_values object.

Missing bidicts in the Standard Library#

The Python standard library actually contains some examples where bidicts could be used for fun and profit (depending on your ideas of fun and profit):

  • The logging module contains a private _levelToName dict which maps integer levels like 10 to their string names like DEBUG. If I had a nickel for every time I wanted that exposed in a bidirectional map (and as a public attribute, no less), I bet I could afford some better turns of phrase.

  • The dis module maintains a mapping from opnames to opcodes dis.opmap and a separate list of opnames indexed by opcode dis.opnames. These could be combined into a single bidict.

  • Python 3’s html.entities module maintains separate html.entities.name2codepoint and html.entities.codepoint2name dicts. These could be combined into a single bidict.

Caveats#

Non-Atomic Mutation#

As with built-in dicts, mutating operations on a bidict are not atomic. If you need to mutate the same bidict from different threads, use a synchronization primitive to coordinate access. 1

1

See also: [2], [3]

Equivalent but distinct Hashables#

Consider the following:

>>> d = {1: int, 1.0: float}

How many items do you expect d to contain? The actual result might surprise you:

>>> len(d)
1

And similarly,

>>> dict([(1, int), (1.0, float), (1+0j, complex), (True, bool)])
{1: <... 'bool'>}
>>> 1.0 in {True}
True

(Note that 1 == 1.0 == 1+0j == True.)

This illustrates that a mapping cannot contain two items with equivalent but distinct keys (and likewise a set cannot contain two equivalent but distinct elements). If an object that is being looked up in a set or mapping is equal to a contained object, the contained object will be found, even if it is distinct.

With a bidict, since values function as keys in the inverse mapping, this behavior occurs in the inverse direction too, and means that a bidict can end up with a different but equivalent key from the corresponding value in its own inverse:

>>> b = bidict({'false': 0})
>>> b.forceput('FALSE', False)
>>> b
bidict({'FALSE': False})
>>> b.inverse
bidict({0: 'FALSE'})

nan as a Key#

In CPython, nan is especially tricky when used as a dictionary key:

>>> d = {float('nan'): 'nan'}
>>> d
{nan: 'nan'}
>>> d[float('nan')]  
Traceback (most recent call last):
    ...
KeyError: nan
>>> d[float('nan')] = 'not overwritten'
>>> d  
{nan: 'nan', nan: 'not overwritten'}

In other Python implementations such as PyPy, nan behaves just like any other dictionary key. But in CPython, beware of this unexpected behavior, which applies to bidicts too. bidict contains no special-case logic for dealing with nan as a key, so bidict’s behavior will match dict’s on whatever runtime you’re using.

See e.g. these docs for more info (search the page for “nan”).


For more in this vein, check out Learning from bidict.

Changelog#

Sponsor through GitHub Sponsors on GitHub

Release Notifications#

Tip: Watch releases on GitHub to be notified when new versions of bidict are released.

0.22.0 (2022-03-23)#

  • Drop support for Python 3.6, which reached end of life on 2021-12-23 and is no longer supported by pip as of pip version 22. Take advantage of this to reduce bidict’s maintenance costs.

  • Use mypy-appeasing explicit re-exports in __init__.py (e.g. import x as x) so that mypy no longer gives you an implicit re-export error if you run it with --no-implicit-reexport (or --strict) against code that imports from bidict.

  • Update the implementations and type annotations of bidict.BidictBase.keys() and bidict.BidictBase.values() to make use of the new BidictKeysView type, which works a bit better with type checkers.

  • Inverse bidict instances are now computed lazily the first time the inverse attribute is accessed rather than being computed eagerly during initialization. (A bidict’s backing, inverse, one-way mapping is still kept in sync eagerly as any mutations are made, to preserve key- and value-uniqueness.)

  • Optimize initializing a bidict with another bidict. In a microbenchmark on Python 3.10, this now performs over 2x faster.

  • Optimize updating an empty bidict with another bidict. In a microbenchmark on Python 3.10, this now performs 60-75% faster.

  • Optimize copy(). In a microbenchmark on Python 3.10, this now performs 10-20x faster.

  • Optimize rolling back failed updates to a bidict in the case that the number of items passed to the update call can be determined to be larger than the bidict being updated. Previously this rollback was O(n) in the number of items passed. Now it is O(1), i.e. unboundedly faster.

  • Optimize bidict.BidictBase.__contains__() (the method called when you run key in mybidict). In a microbenchmark on Python 3.10, this now performs over 3-10x faster in the False case, and at least 50% faster in the True case.

  • Optimize bidict.BidictBase.__eq__() (the method called when you run mybidict == other). In a microbenchmark on Python 3.10, this now performs 15-25x faster for ordered bidicts, and 7-12x faster for unordered bidicts.

  • Optimize equals_order_sensitive(). In a microbenchmark on Python 3.10, this now performs 2x faster for ordered bidicts and 60-90% faster for unordered bidicts.

  • Optimize the MappingView objects returned by bidict.OrderedBidict.keys(), bidict.OrderedBidict.values(), and bidict.OrderedBidict.items() to delegate to backing dict_keys and dict_items objects if available, which are much faster in CPython. For example, in a microbenchmark on Python 3.10, orderedbi.items() == d.items() now performs 30-50x faster.

  • Fix a bug where bidict.BidictBase.__eq__() was always returning False rather than NotImplemented in the case that the argument was not a Mapping, defeating the argument’s own __eq__() if implemented. As a notable example, bidicts now correctly compare equal to unittest.mock.ANY.

  • bidict.BidictBase now adds a __reversed__ implementation to subclasses that don’t have an overridden implementation depending on whether both their backing mappings are Reversible. Previously, a __reversed__ implementation was only added to BidictBase when BidictBase._fwdm_cls was Reversible. So if a BidictBase subclass set its _fwdm_cls to a non-reversible mutable mapping, it would also have to manually set its __reversed__ attribute to None to override the implementation inherited from BidictBase. This is no longer necessary thanks to bidict’s new object.__init_subclass__() logic.

  • The MappingView objects returned by bidict.OrderedBidict.keys(), bidict.OrderedBidict.values(), and bidict.OrderedBidict.items() are now Reversible. (This was already the case for unordered bidicts when running on Python 3.8+.)

  • Add support for Python 3.9-style dict merge operators (PEP 584).

    See the tests for examples.

  • Update docstrings for bidict.BidictBase.keys(), bidict.BidictBase.values(), and bidict.BidictBase.items() to include more details.

  • namedbidict() now exposes the passed-in keyname and valname in the corresponding properties on the generated class.

  • namedbidict() now requires base_type to be a subclass of BidictBase, but no longer requires base_type to provide an _isinv attribute, which BidictBase subclasses no longer provide.

  • When attempting to pickle a bidict’s inverse whose class was dynamically generated, and no reference to the dynamically-generated class has been stored anywhere in sys.modules where pickle can find it, the pickle call is now more likely to succeed rather than failing with a PicklingError.

  • Remove the use of slots from (non-ABC) bidict types.

    This better matches the mapping implementations in Python’s standard library, and significantly reduces code complexity and maintenance burden. The memory savings conferred by using slots are not noticeable unless you’re creating millions of bidict instances anyway, which is an extremely unusual usage pattern.

    Of course, bidicts can still contain millions (or more) items (which is not an unusual usage pattern) without using any more memory than before these changes. Notably, slots are still used in the internal linked list nodes of ordered bidicts to save memory, since as many node instances are created as there are items inserted.

0.21.4 (2021-10-23)#

Explicitly declare support for Python 3.10 as well as some minor internal improvements.

0.21.3 (2021-09-05)#

0.21.2 (2020-09-07)#

0.21.1 (2020-09-07)#

This release was yanked and replaced with the 0.21.2 release, which actually provides the intended changes.

0.21.0 (2020-08-22)#

0.20.0 (2020-07-23)#

The following breaking changes are expected to affect few if any users.

Remove APIs deprecated in the previous release:

0.19.0 (2020-01-09)#

0.18.4 (2020-11-02)#

0.18.3 (2019-09-22)#

0.18.2 (2019-09-08)#

  • Warn that Python 2 support will be dropped in a future release when Python 2 is detected.

0.18.1 (2019-09-03)#

  • Fix a regression introduced by the memory optimizations added in 0.15.0 which caused deepcopied and unpickled bidicts to have their inverses set incorrectly. #94

0.18.0 (2019-02-14)#

  • Rename bidict.BidirectionalMapping.inv to inverse and make bidict.BidictBase.inv an alias for inverse. #86

  • bidict.BidirectionalMapping.__subclasshook__() now requires an inverse attribute rather than an inv attribute for a class to qualify as a virtual subclass. This breaking change is expected to affect few if any users.

  • Add Python 2/3-compatible bidict.compat.collections_abc alias.

  • Stop testing Python 3.4 on CI, and warn when Python 3 < 3.5 is detected rather than Python 3 < 3.3.

    Python 3.4 reaches end of life on 2019-03-18. As of January 2019, 3.4 represents only about 3% of bidict downloads on PyPI Stats.

0.17.5 (2018-11-19)#

Improvements to performance and delegation logic, with minor breaking changes to semi-private APIs.

  • Remove the __delegate__ instance attribute added in the previous release. It was overly general and not worth the cost.

    Instead of checking self.__delegate__ and delegating accordingly each time a possibly-delegating method is called, revert back to using “delegated-to-fwdm” mixin classes (now found in bidict._delegating_mixins), and resurrect a mutable bidict parent class that omits the mixins as bidict.MutableBidict.

  • Rename __repr_delegate__ to _repr_delegate.

0.17.4 (2018-11-14)#

Minor code, interop, and (semi-)private API improvements.

  • OrderedBidict optimizations and code improvements.

    Use bidicts for the backing _fwdm and _invm mappings, obviating the need to store key and value data in linked list nodes.

  • Refactor proxied- (i.e. delegated-) to-_fwdm logic for better composability and interoperability.

    Drop the _Proxied* mixin classes and instead move their methods into BidictBase, which now checks for an object defined by the BidictBase.__delegate__ attribute. The BidictBase.__delegate__ object will be delegated to if the method is available on it, otherwise a default implementation (e.g. inherited from Mapping) will be used otherwise. Subclasses may set __delegate__ = None to opt out.

    Consolidate _MutableBidict into bidict.bidict now that the dropped mixin classes make it unnecessary.

  • Change __repr_delegate__ to simply take a type like dict or list.

  • Upgrade to latest major sortedcontainers version (from v1 to v2) for the SortedBidict Recipes.

  • bidict.compat.{view,iter}{keys,values,items} on Python 2 no longer assumes the target object implements these methods, as they’re not actually part of the Mapping interface, and provides fallback implementations when the methods are unavailable. This allows the SortedBidict Recipes to continue to work with sortedcontainers v2 on Python 2.

0.17.3 (2018-09-18)#

  • Improve packaging by adding a pyproject.toml and by including more supporting files in the distribution. #81

  • Drop pytest-runner and support for running tests via python setup.py test in preference to pytest or python -m pytest.

0.17.2 (2018-04-30)#

Memory usage improvements

  • Use less memory in the linked lists that back OrderedBidicts by storing node data unpacked rather than in (key, value) tuple objects.

0.17.1 (2018-04-28)#

Bugfix Release

Fix a regression in 0.17.0 that could cause erroneous behavior when updating items of an Orderedbidict’s inverse, e.g. some_ordered_bidict.inv[foo] = bar.

0.17.0 (2018-04-25)#

Speedups and memory usage improvements

  • Pass keys(), values(), and items() calls (as well as their iter* and view* counterparts on Python 2) through to the backing _fwdm and _invm dicts so that they run as fast as possible (i.e. at C speed on CPython), rather than using the slower implementations inherited from collections.abc.Mapping.

  • Use weakrefs in the linked lists that back OrderedBidicts to avoid creating strong reference cycles.

    Memory for an ordered bidict that you create can now be reclaimed in CPython as soon as you no longer hold any references to it, rather than having to wait until the next garbage collection. #71

Misc

0.16.0 (2018-04-06)#

Minor code and efficiency improvements to inverted() and bidict._iter._iteritems_args_kw (formerly bidict.pairs()).

Minor Breaking API Changes

The following breaking changes are expected to affect few if any users.

  • Rename bidict.pairs()bidict._iter._iteritems_args_kw.

0.15.0 (2018-03-29)#

Speedups and memory usage improvements

  • Use __slots__ to speed up bidict attribute access and reduce memory usage. On Python 3, instantiating a large number of bidicts now uses ~57% the amount of memory that it used before, and on Python 2 only ~33% the amount of memory that it used before, in a simple but representative benchmark.

  • Use weakrefs to refer to a bidict’s inverse internally, no longer creating a strong reference cycle. Memory for a bidict that you create can now be reclaimed in CPython as soon as you no longer hold any references to it, rather than having to wait for the next garbage collection. See the new bidict Avoids Reference Cycles documentation. #24

  • Make bidict.BidictBase.__eq__() significantly more speed- and memory-efficient when comparing to a non-dict Mapping. (Mapping.__eq__()'s inefficient implementation will now never be used.) The implementation is now more reusable as well.

  • Make bidict.OrderedBidictBase.__iter__() as well as equality comparison slightly faster for ordered bidicts.

Minor Bugfixes

  • namedbidict() now verifies that the provided keyname and valname are distinct, raising ValueError if they are equal.

  • namedbidict() now raises TypeError if the provided base_type is not a BidirectionalMapping.

  • If you create a custom bidict subclass whose _fwdm_cls differs from its _invm_cls (as in the FwdKeySortedBidict example from the SortedBidict Recipes), the inverse bidirectional mapping type (with _fwdm_cls and _invm_cls swapped) is now correctly computed and used automatically for your custom bidict’s inverse bidict.

Misc

  • Classes no longer have to provide an __inverted__ attribute to be considered virtual subclasses of BidirectionalMapping.

  • If bidict.inverted() is passed an object with an __inverted__ attribute, it now ensures it is callable() before returning the result of calling it.

  • __repr__() no longer checks for a __reversed__ method to determine whether to use an ordered or unordered-style repr. It now calls the new __repr_delegate__ instead (which may be overridden if needed), for better composability.

Minor Breaking API Changes

The following breaking changes are expected to affect few if any users.

  • Split back out the BidictBase class from frozenbidict and OrderedBidictBase from FrozenOrderedBidict, reverting the merging of these in 0.14.0. Having e.g. issubclass(bidict, frozenbidict) == True was confusing, so this change restores issubclass(bidict, frozenbidict) == False.

    See the updated Bidict Types Diagram and Polymorphism documentation.

  • Rename:

    • bidict.BidictBase.fwdm._fwdm

    • bidict.BidictBase.invm._invm

    • bidict.BidictBase.fwd_cls._fwdm_cls

    • bidict.BidictBase.inv_cls._invm_cls

    • bidict.BidictBase.isinv._isinv

    Though overriding _fwdm_cls and _invm_cls remains supported (see Extending bidict), this is not a common enough use case to warrant public names. Most users do not need to know or care about any of these.

  • The RAISE, OVERWRITE, and IGNORE duplication policies are no longer available as attributes of DuplicationPolicy, and can now only be accessed as attributes of the bidict module namespace, which was the canonical way to refer to them anyway. It is now no longer possible to create an infinite chain like DuplicationPolicy.RAISE.RAISE.RAISE...

  • Make bidict.pairs() and bidict.inverted() no longer importable from bidict.util, and now only importable from the top-level bidict module. (bidict.util was renamed bidict._util.)

  • Pickling ordered bidicts now requires at least version 2 of the pickle protocol. If you are using Python 3, pickle.DEFAULT_PROTOCOL is 3 anyway, so this will not affect you. However if you are using in Python 2, DEFAULT_PROTOCOL is 0, so you must now explicitly specify the version in your pickle.dumps() calls, e.g. pickle.dumps(ob, 2).

0.14.2 (2017-12-06)#

  • Make initializing (or updating an empty bidict) from only another BidirectionalMapping more efficient by skipping unnecessary duplication checking.

  • Fix accidental ignoring of specified base_type argument when (un)pickling a namedbidict().

  • Fix incorrect inversion of some_named_bidict.inv.<fwdname>_for and some_named_bidict.inv.<invname>_for.

  • Only warn when an unsupported Python version is detected (e.g. Python < 2.7) rather than raising AssertionError.

0.14.1 (2017-11-28)#

  • Fix a bug introduced in 0.14.0 where hashing a frozenbidict’s inverse (e.g. f = frozenbidict(); {f.inv: '...'}) would cause an AttributeError.

  • Fix a bug introduced in 0.14.0 for Python 2 users where attempting to call viewitems() would cause a TypeError. #48

0.14.0 (2017-11-20)#

  • Fix a bug where bidict’s default on_dup_kv policy was set to RAISE, rather than matching whatever on_dup_val policy was in effect as was documented.

  • Fix a bug that could happen when using Python’s optimization (-O) flag that could leave an ordered bidict in an inconsistent state when dealing with duplicated, overwritten keys or values. If you do not use optimizations (specifically, skipping assert statements), this would not have affected you.

  • Fix a bug introduced by the optimizations in 0.13.0 that could cause a frozen bidict that compared equal to another mapping to have a different hash value from the other mapping, violating Python’s object model. This would only have affected you if you were inserting a frozen bidict and some other immutable mapping that it compared equal to into the same set or mapping.

  • Add equals_order_sensitive().

  • Reduce the memory usage of ordered bidicts.

  • Make copying of ordered bidicts faster.

  • Improvements to tests and CI, including:

    • Test on Windows

    • Test with PyPy3

    • Test with CPython 3.7-dev

    • Test with optimization flags

    • Require pylint to pass

Breaking API Changes

This release includes multiple API simplifications and improvements.

  • Rename:

    so that these now match the case of collections.OrderedDict.

    The names of the bidict, namedbidict(), and frozenbidict classes have been retained as all-lowercase so that they continue to match the case of dict, namedtuple(), and frozenset, respectively.

  • The ON_DUP_VAL duplication policy value for on_dup_kv has been removed. Use None instead.

  • Merge frozenbidict and BidictBase together and remove BidictBase. frozenbidict is now the concrete base class that all other bidict types derive from. See the updated Bidict Types Diagram.

  • Merge frozenbidict and FrozenBidictBase together and remove FrozenBidictBase. See the updated Bidict Types Diagram.

  • Merge frozenorderedbidict and OrderedBidictBase together into a single FrozenOrderedBidict class and remove OrderedBidictBase. OrderedBidict now extends FrozenOrderedBidict to add mutable behavior. See the updated Bidict Types Diagram.

  • Make __eq__() always perform an order-insensitive equality test, even if the other mapping is ordered.

    Previously, __eq__() was only order-sensitive for other OrderedBidictBase subclasses, and order-insensitive otherwise.

    Use the new equals_order_sensitive() method for order-sensitive equality comparison.

  • orderedbidict._should_compare_order_sensitive() has been removed.

  • frozenorderedbidict._HASH_NITEMS_MAX has been removed. Since its hash value must be computed from all contained items (so that hash results are consistent with equality comparisons against unordered mappings), the number of items that influence the hash value should not be limitable.

  • frozenbidict._USE_ITEMSVIEW_HASH has been removed, and frozenbidict.compute_hash() now uses collections.ItemsView._hash() to compute the hash always, not just when running on PyPy.

    Override frozenbidict.compute_hash() to return hash(frozenset(iteritems(self))) if you prefer the old default behavior on CPython, which takes linear rather than constant space, but which uses the frozenset_hash routine (implemented in setobject.c) rather than the pure Python ItemsView._hash() routine.

  • loosebidict and looseorderedbidict have been removed. A simple recipe to implement equivalents yourself is now given in Extending bidict.

  • Rename FrozenBidictBase._compute_hash()frozenbidict.compute_hash().

  • Rename DuplicationBehaviorDuplicationPolicy.

  • Rename:

    • BidictBase._fwd_class.fwd_cls

    • BidictBase._inv_class.inv_cls

    • BidictBase._on_dup_keyon_dup_key

    • BidictBase._on_dup_valon_dup_val

    • BidictBase._on_dup_kvon_dup_kv

0.13.1 (2017-03-15)#

  • Fix regression introduced by the new __subclasshook__() functionality in 0.13.0 so that issubclass(OldStyleClass, BidirectionalMapping) once again works with old-style classes, returning False rather than raising AttributeError #41

0.13.0 (2017-01-19)#

  • Support Python 3.6.

    (Earlier versions of bidict should work fine on 3.6, but it is officially supported starting in this version.)

  • BidirectionalMapping has been refactored into an abstract base class, following the way collections.abc.Mapping works. The concrete method implementations it used to provide have been moved into a new BidictBase subclass.

    BidirectionalMapping now also implements __subclasshook__(), so any class that provides a conforming set of attributes (enumerated in _subclsattrs) will be considered a BidirectionalMapping subclass automatically.

  • OrderedBidirectionalMapping has been renamed to OrderedBidictBase, to better reflect its function. (It is not an ABC.)

  • A new FrozenBidictBase class has been factored out of frozenbidict and frozenorderedbidict. This implements common behavior such as caching the result of __hash__ after the first call.

  • The hash implementations of frozenbidict and frozenorderedbidict. have been reworked to improve performance and flexibility. frozenorderedbidict’s hash implementation is now order-sensitive.

    See frozenbidict._compute_hash() and frozenorderedbidict._compute_hash for more documentation of the changes, including the new frozenbidict._USE_ITEMSVIEW_HASH and frozenorderedbidict._HASH_NITEMS_MAX attributes. If you have an interesting use case that requires overriding these, or suggestions for an alternative implementation, please share your feedback.

  • Add _fwd_class and _inv_class attributes representing the backing Mapping types used internally to store the forward and inverse dictionaries, respectively.

    This allows creating custom bidict types with extended functionality simply by overriding these attributes in a subclass.

    See the new Extending bidict documentation for examples.

  • Pass any parameters passed to popitem() through to _fwd.popitem for greater extensibility.

  • More concise repr strings for empty bidicts.

    e.g. bidict() rather than bidict({}) and orderedbidict() rather than orderedbidict([]).

  • Add bidict.compat.PYPY and remove unused bidict.compat.izip_longest.

0.12.0 (2016-07-03)#

  • New/renamed exceptions:

  • put() now accepts on_dup_key, on_dup_val, and on_dup_kv keyword args which allow you to override the default policy when the key or value of a given item duplicates any existing item’s. These can take the following values:

    on_dup_kv can also take ON_DUP_VAL.

    If not provided, put() uses the RAISE policy by default.

  • New putall() method provides a bulk put() API, allowing you to override the default duplication handling policy that update() uses.

  • update() now fails clean, so if an update() call raises a DuplicationError, you can now be sure that none of the given items was inserted.

    Previously, all of the given items that were processed before the one causing the failure would have been inserted, and no facility was provided to recover which items were inserted and which weren’t, nor to revert any changes made by the failed update() call. The new behavior makes it easier to reason about and control the effects of failed update() calls.

    The new putall() method also fails clean.

    Internally, this is implemented by storing a log of changes made while an update is being processed, and rolling back the changes when one of them is found to cause an error. This required reimplementing orderedbidict on top of two dicts and a linked list, rather than two OrderedDicts, since OrderedDict does not expose its backing linked list.

  • orderedbidict.move_to_end() now works on Python < 3.2 as a result of the new orderedbidict implementation.

  • Add

    • bidict.compat.viewkeys

    • bidict.compat.viewvalues

    • bidict.compat.iterkeys

    • bidict.compat.itervalues

    • bidict.compat.izip

    • bidict.compat.izip_longest

    to complement the existing bidict.compat.iteritems and bidict.compat.viewitems compatibility helpers.

  • More efficient implementations of bidict.pairs(), inverted(), and copy().

  • Implement __copy__() for use with the copy module.

  • Fix issue preventing a client class from inheriting from loosebidict. #34

  • Add benchmarking to tests.

  • Drop official support for CPython 3.3. (It may continue to work, but is no longer being tested.)

Breaking API Changes

  • Rename KeyExistsExceptionKeyDuplicationError and ValueExistsExceptionValueDuplicationError.

  • When overwriting the key of an existing value in an orderedbidict, the position of the existing item is now preserved, overwriting the key of the existing item in place, rather than moving the item to the end. This now matches the behavior of overwriting the value of an existing key, which has always preserved the position of the existing item. (If inserting an item whose key duplicates that of one existing item and whose value duplicates that of another, the existing item whose value is duplicated is still dropped, and the existing item whose key is duplicated still gets its value overwritten in place, as before.)

    For example:

    >>> from bidict import orderedbidict  
    >>> o = orderedbidict([(0, 1), (2, 3)])  
    >>> o.forceput(4, 1)  
    

    previously would have resulted in:

    >>> o  
    orderedbidict([(2, 3), (4, 1)])
    

    but now results in:

    >>> o  
    orderedbidict([(4, 1), (2, 3)])
    

0.11.0 (2016-02-05)#

0.10.0.post1 (2015-12-23)#

  • Minor documentation fixes and improvements.

0.10.0 (2015-12-23)#

  • Remove several features in favor of keeping the API simpler and the code more maintainable.

  • In the interest of protecting data safety more proactively, by default bidict now raises an error on attempting to insert a non-unique value, rather than allowing its associated key to be silently overwritten. See discussion in #21.

  • New forceupdate() method provides a bulk forceput() operation.

  • Fix bugs in pop and setdefault which could leave a bidict in an inconsistent state.

Breaking API Changes

  • Remove bidict.__invert__, and with it, support for the ~b syntax. Use inv instead. #19

  • Remove support for the slice syntax. Use b.inv[val] rather than b[:val]. #19

  • Remove bidict.invert. Use inv rather than inverting a bidict in place. #20

  • Raise ValueExistsException when attempting to insert a mapping with a non-unique key. #21

  • Rename collapsingbidictloosebidict now that it suppresses ValueExistsException rather than the less general CollapseException. #21

  • CollapseException has been subsumed by ValueExistsException. #21

  • put() now raises KeyExistsException when attempting to insert an already-existing key, and ValueExistsException when attempting to insert an already-existing value.

0.9.0.post1 (2015-06-06)#

  • Fix metadata missing in the 0.9.0rc0 release.

0.9.0rc0 (2015-05-30)#

Breaking API Changes

  • Move bidict.iteritems() and bidict.viewitems() to new bidict.compat module.

  • Move bidict.inverted to new bidict.util module (still available from top-level bidict module as well).

  • Move bidict.fancy_iteritems()bidict.util.pairs() (also available from top level as bidict.pairs()).

  • Rename bidict.namedbidict()'s bidict_type argument → base_type.

API#

This page contains auto-generated documentation from the bidict source code.

bidict#

The bidirectional mapping library for Python.


bidict by example:

>>> from bidict import bidict
>>> element_by_symbol = bidict({'H': 'hydrogen'})
>>> element_by_symbol['H']
'hydrogen'
>>> element_by_symbol.inverse['hydrogen']
'H'

Please see https://github.com/jab/bidict for the most up-to-date code and https://bidict.readthedocs.io for the most up-to-date documentation if you are reading this elsewhere.


class bidict.BidirectionalMapping#

Bases: Mapping[bidict._typing.KT, bidict._typing.VT]

Abstract base class for bidirectional mapping types.

Extends collections.abc.Mapping primarily by adding the (abstract) inverse property, which implementors of BidirectionalMapping should override to return a reference to the inverse BidirectionalMapping instance.

__abstractmethods__ = frozenset({'__getitem__', '__iter__', '__len__', 'inverse'})#
__class_getitem__ = <bound method GenericAlias of <class 'bidict.BidirectionalMapping'>>#
__contains__(key)#
__eq__(other)#

Return self==value.

abstract __getitem__(key)#
__hash__ = None#
classmethod __init_subclass__(*args, **kwargs)#

This method is called when a class is subclassed.

The default implementation does nothing. It may be overridden to extend subclasses.

__inverted__()[source]#

Get an iterator over the items in inverse.

This is functionally equivalent to iterating over the items in the forward mapping and inverting each one on the fly, but this provides a more efficient implementation: Assuming the already-inverted items are stored in inverse, just return an iterator over them directly.

Providing this default implementation enables external functions, particularly inverted(), to use this optimized implementation when available, instead of having to invert on the fly.

See also bidict.inverted()

Return type

Iterator[Tuple[bidict._typing.VT, bidict._typing.KT]]

abstract __iter__()#
abstract __len__()#
__module__ = 'bidict'#
__orig_bases__ = (typing.Mapping[~KT, ~VT],)#
__parameters__ = (~KT, ~VT)#
__reversed__ = None#
__slots__ = ()#
classmethod __subclasshook__(C)#

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

get(k[, d]) D[k] if k in D, else d.  d defaults to None.#
abstract property inverse: bidict.BidirectionalMapping[bidict._typing.VT, bidict._typing.KT]#

The inverse of this bidirectional mapping instance.

See also bidict.BidictBase.inverse, bidict.BidictBase.inv

Raises

NotImplementedError – Meant to be overridden in subclasses.

items() a set-like object providing a view on D's items#
keys() a set-like object providing a view on D's keys#
values() an object providing a view on D's values#
class bidict.MutableBidirectionalMapping#

Bases: bidict.BidirectionalMapping[bidict._typing.KT, bidict._typing.VT], MutableMapping[bidict._typing.KT, bidict._typing.VT]

Abstract base class for mutable bidirectional mapping types.

__abstractmethods__ = frozenset({'__delitem__', '__getitem__', '__iter__', '__len__', '__setitem__', 'inverse'})#
__annotations__ = {}#
__class_getitem__ = <bound method GenericAlias of <class 'bidict.MutableBidirectionalMapping'>>#
__contains__(key)#
abstract __delitem__(key)#
__eq__(other)#

Return self==value.

abstract __getitem__(key)#
__hash__ = None#
classmethod __init_subclass__(*args, **kwargs)#

This method is called when a class is subclassed.

The default implementation does nothing. It may be overridden to extend subclasses.

__inverted__()#

Get an iterator over the items in inverse.

This is functionally equivalent to iterating over the items in the forward mapping and inverting each one on the fly, but this provides a more efficient implementation: Assuming the already-inverted items are stored in inverse, just return an iterator over them directly.

Providing this default implementation enables external functions, particularly inverted(), to use this optimized implementation when available, instead of having to invert on the fly.

See also bidict.inverted()

Return type

Iterator[Tuple[bidict._typing.VT, bidict._typing.KT]]

abstract __iter__()#
abstract __len__()#
__module__ = 'bidict'#
__orig_bases__ = (bidict.BidirectionalMapping[~KT, ~VT], typing.MutableMapping[~KT, ~VT])#
__parameters__ = (~KT, ~VT)#
__reversed__ = None#
abstract __setitem__(key, value)#
__slots__ = ()#
classmethod __subclasshook__(C)#

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

clear() None.  Remove all items from D.#
get(k[, d]) D[k] if k in D, else d.  d defaults to None.#
abstract property inverse: bidict.BidirectionalMapping[bidict._typing.VT, bidict._typing.KT]#

The inverse of this bidirectional mapping instance.

See also bidict.BidictBase.inverse, bidict.BidictBase.inv

Raises

NotImplementedError – Meant to be overridden in subclasses.

items() a set-like object providing a view on D's items#
keys() a set-like object providing a view on D's keys#
pop(k[, d]) v, remove specified key and return the corresponding value.#

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() (k, v), remove and return some (key, value) pair#

as a 2-tuple; but raise KeyError if D is empty.

setdefault(k[, d]) D.get(k,d), also set D[k]=d if k not in D#
update([E, ]**F) None.  Update D from mapping/iterable E and F.#

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values() an object providing a view on D's values#
class bidict.BidictBase(*args, **kw)#

Bases: bidict.BidirectionalMapping[bidict._typing.KT, bidict._typing.VT]

Base class implementing BidirectionalMapping.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

__abstractmethods__ = frozenset({})#
__annotations__ = {'__reversed__': typing.Any, '_fwdm': typing.MutableMapping[~KT, ~VT], '_fwdm_cls': typing.ClassVar[typing.Type[typing.MutableMapping[typing.Any, typing.Any]]], '_inv_cls': 't.ClassVar[t.Type[BidictBase[t.Any, t.Any]]]', '_invm': typing.MutableMapping[~VT, ~KT], '_invm_cls': typing.ClassVar[typing.Type[typing.MutableMapping[typing.Any, typing.Any]]], '_repr_delegate': typing.ClassVar[typing.Any]}#
__class_getitem__ = <bound method GenericAlias of <class 'bidict.BidictBase'>>#
__contains__(key)[source]#

True if the mapping contains the specified key, else False.

Parameters

key (Any) –

Return type

bool

__copy__()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

__dict__ = mappingproxy({'__module__': 'bidict', '__annotations__': {'_fwdm': typing.MutableMapping[~KT, ~VT], '_invm': typing.MutableMapping[~VT, ~KT], '_fwdm_cls': typing.ClassVar[typing.Type[typing.MutableMapping[typing.Any, typing.Any]]], '_invm_cls': typing.ClassVar[typing.Type[typing.MutableMapping[typing.Any, typing.Any]]], '_inv_cls': 't.ClassVar[t.Type[BidictBase[t.Any, t.Any]]]', '_repr_delegate': typing.ClassVar[typing.Any], '__reversed__': typing.Any}, '__doc__': 'Base class implementing :class:`BidirectionalMapping`.', 'on_dup': OnDup(key=OD.DROP_OLD, val=OD.RAISE, kv=OD.RAISE), '_fwdm_cls': <class 'dict'>, '_invm_cls': <class 'dict'>, '_repr_delegate': <class 'dict'>, '__init_subclass__': <classmethod(<function BidictBase.__init_subclass__>)>, '_init_class': <classmethod(<function BidictBase._init_class>)>, '_set_reversed': <classmethod(<function BidictBase._set_reversed>)>, '_ensure_inv_cls': <classmethod(<function BidictBase._ensure_inv_cls>)>, '_make_inv_cls': <classmethod(<function BidictBase._make_inv_cls>)>, '_inv_cls_dict_diff': <classmethod(<function BidictBase._inv_cls_dict_diff>)>, '__init__': <function BidictBase.__init__>, 'inverse': <property object>, '_make_inverse': <function BidictBase._make_inverse>, 'inv': <property object>, '__repr__': <function BidictBase.__repr__>, 'values': <function BidictBase.values>, 'keys': <function BidictBase.keys>, 'items': <function BidictBase.items>, '__contains__': <function BidictBase.__contains__>, '__eq__': <function BidictBase.__eq__>, 'equals_order_sensitive': <function BidictBase.equals_order_sensitive>, '_dedup': <function BidictBase._dedup>, '_prep_write': <function BidictBase._prep_write>, '_update': <function BidictBase._update>, 'copy': <function BidictBase.copy>, '_from_other': <staticmethod(<function BidictBase._from_other>)>, '_init_from': <function BidictBase._init_from>, '__copy__': <function BidictBase.copy>, '__or__': <function BidictBase.__or__>, '__ror__': <function BidictBase.__ror__>, '__len__': <function BidictBase.__len__>, '__iter__': <function BidictBase.__iter__>, '__getitem__': <function BidictBase.__getitem__>, '__reduce__': <function BidictBase.__reduce__>, '__orig_bases__': (bidict.BidirectionalMapping[~KT, ~VT],), '__dict__': <attribute '__dict__' of 'BidictBase' objects>, '__weakref__': <attribute '__weakref__' of 'BidictBase' objects>, '__hash__': None, '__parameters__': (~KT, ~VT), '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data object>, '_inv_cls': <class 'bidict.BidictBase'>, '__reversed__': <function _fwdm_reversed>})#
__eq__(other)[source]#

x.__eq__(other) ⟺ x == other

Equivalent to dict(x.items()) == dict(other.items()) but more efficient.

Note that bidict's __eq__() implementation is inherited by subclasses, in particular by the ordered bidict subclasses, so even with ordered bidicts, == comparison is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive).

See also equals_order_sensitive()

Parameters

other (object) –

Return type

bool

__getitem__(key)[source]#

x.__getitem__(key) ⟺ x[key]

Parameters

key (bidict._typing.KT) –

Return type

bidict._typing.VT

__hash__ = None#
__init__(*args, **kw)[source]#

Make a new bidirectional mapping. The signature behaves like that of dict. Items passed in are added in the order they are passed, respecting the on_dup class attribute in the process.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

classmethod __init_subclass__()[source]#

This method is called when a class is subclassed.

The default implementation does nothing. It may be overridden to extend subclasses.

Return type

None

__inverted__()#

Get an iterator over the items in inverse.

This is functionally equivalent to iterating over the items in the forward mapping and inverting each one on the fly, but this provides a more efficient implementation: Assuming the already-inverted items are stored in inverse, just return an iterator over them directly.

Providing this default implementation enables external functions, particularly inverted(), to use this optimized implementation when available, instead of having to invert on the fly.

See also bidict.inverted()

Return type

Iterator[Tuple[bidict._typing.VT, bidict._typing.KT]]

__iter__()[source]#

Iterator over the contained keys.

Return type

Iterator[bidict._typing.KT]

__len__()[source]#

The number of contained items.

Return type

int

__module__ = 'bidict'#
__or__(other)[source]#

Return self|other.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__orig_bases__ = (bidict.BidirectionalMapping[~KT, ~VT],)#
__parameters__ = (~KT, ~VT)#
__reduce__()[source]#

Return state information for pickling.

Return type

Tuple[Any, …]

__repr__()[source]#

See repr().

Return type

str

__reversed__()#

Iterator over the contained keys in reverse order.

Parameters

self (bidict.BidictBase[bidict._typing.KT, Any]) –

Return type

Iterator[bidict._typing.KT]

__ror__(other)[source]#

Return other|self.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__slots__ = ()#
classmethod __subclasshook__(C)#

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

__weakref__#

list of weak references to the object (if defined)

copy()[source]#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

equals_order_sensitive(other)[source]#

Order-sensitive equality check.

See also __eq__() is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive)

Parameters

other (object) –

Return type

bool

get(k[, d]) D[k] if k in D, else d.  d defaults to None.#
property inv: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

Alias for inverse.

property inverse: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

The inverse of this bidirectional mapping instance.

items()[source]#

A set-like object providing a view on the contained items.

When b._fwdm is a dict, b.items() returns a dict_items object that behaves exactly the same as collections.abc.ItemsView(b), except for:

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

ItemsView[bidict._typing.KT, bidict._typing.VT]

keys()[source]#

A set-like object providing a view on the contained keys.

When b._fwdm is a dict, b.keys() returns a dict_keys object that behaves exactly the same as collections.abc.KeysView(b), except for

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

KeysView[bidict._typing.KT]

on_dup = OnDup(key=OD.DROP_OLD, val=OD.RAISE, kv=OD.RAISE)#
values()[source]#

A set-like object providing a view on the contained values.

Since the values of a bidict are equivalent to the keys of its inverse, this method returns a set-like object for this bidict’s values rather than just a collections.abc.ValuesView. This object supports set operations like union and difference, and constant- rather than linear-time containment checks, and is no more expensive to provide than the less capable collections.abc.ValuesView would be.

See keys() for more information.

Return type

bidict.BidictKeysView[bidict._typing.VT]

class bidict.GeneratedBidictInverse#

Bases: object

Base class for dynamically-generated inverse bidict classes.

__dict__ = mappingproxy({'__module__': 'bidict', '__doc__': 'Base class for dynamically-generated inverse bidict classes.', '__dict__': <attribute '__dict__' of 'GeneratedBidictInverse' objects>, '__weakref__': <attribute '__weakref__' of 'GeneratedBidictInverse' objects>, '__annotations__': {}})#
__module__ = 'bidict'#
__weakref__#

list of weak references to the object (if defined)

class bidict.BidictKeysView(mapping)#

Bases: KeysView[bidict._typing.KT], ValuesView[bidict._typing.KT]

Since the keys of a bidict are the values of its inverse (and vice versa), the ValuesView result of calling bi.values() is also a KeysView of bi.inverse.

__abstractmethods__ = frozenset({})#
__and__(other)#
__annotations__ = {}#
__class_getitem__ = <bound method GenericAlias of <class 'bidict.BidictKeysView'>>#
__contains__(key)#
__dict__ = mappingproxy({'__module__': 'bidict', '__doc__': 'Since the keys of a bidict are the values of its inverse (and vice versa),\n    the :class:`~collections.abc.ValuesView` result of calling *bi.values()*\n    is also a :class:`~collections.abc.KeysView` of *bi.inverse*.\n    ', '__orig_bases__': (typing.KeysView[~KT], typing.ValuesView[~KT]), '__dict__': <attribute '__dict__' of 'BidictKeysView' objects>, '__weakref__': <attribute '__weakref__' of 'BidictKeysView' objects>, '__parameters__': (~KT,), '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data object>, '__annotations__': {}})#
__eq__(other)#

Return self==value.

__ge__(other)#

Return self>=value.

__gt__(other)#

Return self>value.

__hash__ = None#
__init__(mapping)#
classmethod __init_subclass__(*args, **kwargs)#

This method is called when a class is subclassed.

The default implementation does nothing. It may be overridden to extend subclasses.

__iter__()#
__le__(other)#

Return self<=value.

__len__()#
__lt__(other)#

Return self<value.

__module__ = 'bidict'#
__or__(other)#

Return self|value.

__orig_bases__ = (typing.KeysView[~KT], typing.ValuesView[~KT])#
__parameters__ = (~KT,)#
__rand__(other)#
__repr__()#

Return repr(self).

__ror__(other)#

Return value|self.

__rsub__(other)#
__rxor__(other)#
__slots__ = ()#
__sub__(other)#
classmethod __subclasshook__(C)#

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

__weakref__#

list of weak references to the object (if defined)

__xor__(other)#
isdisjoint(other)#

Return True if two sets have a null intersection.

class bidict.MutableBidict(*args, **kw)#

Bases: bidict.BidictBase[bidict._typing.KT, bidict._typing.VT], bidict.MutableBidirectionalMapping[bidict._typing.KT, bidict._typing.VT]

Base class for mutable bidirectional mappings.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

__abstractmethods__ = frozenset({})#
__annotations__ = {}#
__class_getitem__ = <bound method GenericAlias of <class 'bidict.MutableBidict'>>#
__contains__(key)#

True if the mapping contains the specified key, else False.

Parameters

key (Any) –

Return type

bool

__copy__()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

__delitem__(key)[source]#

x.__delitem__(y) ⟺ del x[y]

Parameters

key (bidict._typing.KT) –

Return type

None

__dict__ = mappingproxy({'__module__': 'bidict', '__doc__': 'Base class for mutable bidirectional mappings.', '_pop': <function MutableBidict._pop>, '__delitem__': <function MutableBidict.__delitem__>, '__setitem__': <function MutableBidict.__setitem__>, 'put': <function MutableBidict.put>, 'forceput': <function MutableBidict.forceput>, 'clear': <function MutableBidict.clear>, 'pop': <function MutableBidict.pop>, 'popitem': <function MutableBidict.popitem>, 'update': <function MutableBidict.update>, 'forceupdate': <function MutableBidict.forceupdate>, '__ior__': <function MutableBidict.__ior__>, 'putall': <function MutableBidict.putall>, '__orig_bases__': (bidict.BidictBase[~KT, ~VT], bidict.MutableBidirectionalMapping[~KT, ~VT]), '__parameters__': (~KT, ~VT), '_inv_cls': <class 'bidict.MutableBidict'>, '__reversed__': <function _fwdm_reversed>, '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data object>, '__annotations__': {}})#
__eq__(other)#

x.__eq__(other) ⟺ x == other

Equivalent to dict(x.items()) == dict(other.items()) but more efficient.

Note that bidict's __eq__() implementation is inherited by subclasses, in particular by the ordered bidict subclasses, so even with ordered bidicts, == comparison is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive).

See also equals_order_sensitive()

Parameters

other (object) –

Return type

bool

__getitem__(key)#

x.__getitem__(key) ⟺ x[key]

Parameters

key (bidict._typing.KT) –

Return type

bidict._typing.VT

__hash__ = None#
__init__(*args, **kw)#

Make a new bidirectional mapping. The signature behaves like that of dict. Items passed in are added in the order they are passed, respecting the on_dup class attribute in the process.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

classmethod __init_subclass__()#

This method is called when a class is subclassed.

The default implementation does nothing. It may be overridden to extend subclasses.

Return type

None

__inverted__()#

Get an iterator over the items in inverse.

This is functionally equivalent to iterating over the items in the forward mapping and inverting each one on the fly, but this provides a more efficient implementation: Assuming the already-inverted items are stored in inverse, just return an iterator over them directly.

Providing this default implementation enables external functions, particularly inverted(), to use this optimized implementation when available, instead of having to invert on the fly.

See also bidict.inverted()

Return type

Iterator[Tuple[bidict._typing.VT, bidict._typing.KT]]

__ior__(other)[source]#

Return self|=other.

Parameters

other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict.MutableBidict[bidict._typing.KT, bidict._typing.VT]

__iter__()#

Iterator over the contained keys.

Return type

Iterator[bidict._typing.KT]

__len__()#

The number of contained items.

Return type

int

__module__ = 'bidict'#
__or__(other)#

Return self|other.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__orig_bases__ = (bidict.BidictBase[~KT, ~VT], bidict.MutableBidirectionalMapping[~KT, ~VT])#
__parameters__ = (~KT, ~VT)#
__reduce__()#

Return state information for pickling.

Return type

Tuple[Any, …]

__repr__()#

See repr().

Return type

str

__reversed__()#

Iterator over the contained keys in reverse order.

Parameters

self (bidict.BidictBase[bidict._typing.KT, Any]) –

Return type

Iterator[bidict._typing.KT]

__ror__(other)#

Return other|self.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__setitem__(key, val)[source]#

Set the value for key to val.

If key is already associated with val, this is a no-op.

If key is already associated with a different value, the old value will be replaced with val, as with dict’s __setitem__().

If val is already associated with a different key, an exception is raised to protect against accidental removal of the key that’s currently associated with val.

Use put() instead if you want to specify different behavior in the case that the provided key or value duplicates an existing one. Or use forceput() to unconditionally associate key with val, replacing any existing items as necessary to preserve uniqueness.

Raises
Parameters
  • key (bidict._typing.KT) –

  • val (bidict._typing.VT) –

Return type

None

__slots__ = ()#
classmethod __subclasshook__(C)#

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

__weakref__#

list of weak references to the object (if defined)

clear()[source]#

Remove all items.

Return type

None

copy()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

equals_order_sensitive(other)#

Order-sensitive equality check.

See also __eq__() is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive)

Parameters

other (object) –

Return type

bool

forceput(key, val)[source]#

Associate key with val unconditionally.

Replace any existing mappings containing key key or value val as necessary to preserve uniqueness.

Parameters
  • key (bidict._typing.KT) –

  • val (bidict._typing.VT) –

Return type

None

forceupdate(*args, **kw)[source]#

Like a bulk forceput().

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

get(k[, d]) D[k] if k in D, else d.  d defaults to None.#
property inv: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

Alias for inverse.

property inverse: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

The inverse of this bidirectional mapping instance.

items()#

A set-like object providing a view on the contained items.

When b._fwdm is a dict, b.items() returns a dict_items object that behaves exactly the same as collections.abc.ItemsView(b), except for:

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

ItemsView[bidict._typing.KT, bidict._typing.VT]

keys()#

A set-like object providing a view on the contained keys.

When b._fwdm is a dict, b.keys() returns a dict_keys object that behaves exactly the same as collections.abc.KeysView(b), except for

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

KeysView[bidict._typing.KT]

on_dup = OnDup(key=OD.DROP_OLD, val=OD.RAISE, kv=OD.RAISE)#
pop(key, default=MissingT.MISSING)[source]#

x.pop(k[, d]) → v

Remove specified key and return the corresponding value.

Raises

KeyError – if key is not found and no default is provided.

Parameters
  • key (bidict._typing.KT) –

  • default (Union[bidict._typing.DT, bidict._typing.MissingT]) –

Return type

Union[bidict._typing.VT, bidict._typing.DT]

popitem()[source]#

x.popitem() → (k, v)

Remove and return some item as a (key, value) pair.

Raises

KeyError – if x is empty.

Return type

Tuple[bidict._typing.KT, bidict._typing.VT]

put(key, val, on_dup=OnDup(key=OD.RAISE, val=OD.RAISE, kv=OD.RAISE))[source]#

Associate key with val, honoring the OnDup given in on_dup.

For example, if on_dup is ON_DUP_RAISE, then key will be associated with val if and only if key is not already associated with an existing value and val is not already associated with an existing key, otherwise an exception will be raised.

If key is already associated with val, this is a no-op.

Raises
Parameters
  • key (bidict._typing.KT) –

  • val (bidict._typing.VT) –

  • on_dup (bidict.OnDup) –

Return type

None

putall(items, on_dup=OnDup(key=OD.RAISE, val=OD.RAISE, kv=OD.RAISE))[source]#

Like a bulk put().

If one of the given items causes an exception to be raised, none of the items is inserted.

Parameters
Return type

None

setdefault(k[, d]) D.get(k,d), also set D[k]=d if k not in D#
update(*args, **kw)[source]#

Like calling putall() with self.on_dup passed for on_dup.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

values()#

A set-like object providing a view on the contained values.

Since the values of a bidict are equivalent to the keys of its inverse, this method returns a set-like object for this bidict’s values rather than just a collections.abc.ValuesView. This object supports set operations like union and difference, and constant- rather than linear-time containment checks, and is no more expensive to provide than the less capable collections.abc.ValuesView would be.

See keys() for more information.

Return type

bidict.BidictKeysView[bidict._typing.VT]

class bidict.bidict(*args, **kw)#

Bases: bidict.MutableBidict[bidict._typing.KT, bidict._typing.VT]

The main bidirectional mapping type.

See Introduction and Basic Usage to get started (also available at https://bidict.rtfd.io).

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

__abstractmethods__ = frozenset({})#
__annotations__ = {}#
__class_getitem__ = <bound method GenericAlias of <class 'bidict.bidict'>>#
__contains__(key)#

True if the mapping contains the specified key, else False.

Parameters

key (Any) –

Return type

bool

__copy__()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

__delitem__(key)#

x.__delitem__(y) ⟺ del x[y]

Parameters

key (bidict._typing.KT) –

Return type

None

__dict__ = mappingproxy({'__module__': 'bidict', '__doc__': 'The main bidirectional mapping type.\n\n    See :ref:`intro:Introduction` and :ref:`basic-usage:Basic Usage`\n    to get started (also available at https://bidict.rtfd.io).\n    ', '__orig_bases__': (bidict.MutableBidict[~KT, ~VT],), '__parameters__': (~KT, ~VT), '_inv_cls': <class 'bidict.bidict'>, '__reversed__': <function _fwdm_reversed>, '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data object>, '__annotations__': {}})#
__eq__(other)#

x.__eq__(other) ⟺ x == other

Equivalent to dict(x.items()) == dict(other.items()) but more efficient.

Note that bidict's __eq__() implementation is inherited by subclasses, in particular by the ordered bidict subclasses, so even with ordered bidicts, == comparison is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive).

See also equals_order_sensitive()

Parameters

other (object) –

Return type

bool

__getitem__(key)#

x.__getitem__(key) ⟺ x[key]

Parameters

key (bidict._typing.KT) –

Return type

bidict._typing.VT

__hash__ = None#
__init__(*args, **kw)#

Make a new bidirectional mapping. The signature behaves like that of dict. Items passed in are added in the order they are passed, respecting the on_dup class attribute in the process.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

classmethod __init_subclass__()#

This method is called when a class is subclassed.

The default implementation does nothing. It may be overridden to extend subclasses.

Return type

None

__inverted__()#

Get an iterator over the items in inverse.

This is functionally equivalent to iterating over the items in the forward mapping and inverting each one on the fly, but this provides a more efficient implementation: Assuming the already-inverted items are stored in inverse, just return an iterator over them directly.

Providing this default implementation enables external functions, particularly inverted(), to use this optimized implementation when available, instead of having to invert on the fly.

See also bidict.inverted()

Return type

Iterator[Tuple[bidict._typing.VT, bidict._typing.KT]]

__ior__(other)#

Return self|=other.

Parameters

other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict.MutableBidict[bidict._typing.KT, bidict._typing.VT]

__iter__()#

Iterator over the contained keys.

Return type

Iterator[bidict._typing.KT]

__len__()#

The number of contained items.

Return type

int

__module__ = 'bidict'#
__or__(other)#

Return self|other.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__orig_bases__ = (bidict.MutableBidict[~KT, ~VT],)#
__parameters__ = (~KT, ~VT)#
__reduce__()#

Return state information for pickling.

Return type

Tuple[Any, …]

__repr__()#

See repr().

Return type

str

__reversed__()#

Iterator over the contained keys in reverse order.

Parameters

self (bidict.BidictBase[bidict._typing.KT, Any]) –

Return type

Iterator[bidict._typing.KT]

__ror__(other)#

Return other|self.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__setitem__(key, val)#

Set the value for key to val.

If key is already associated with val, this is a no-op.

If key is already associated with a different value, the old value will be replaced with val, as with dict’s __setitem__().

If val is already associated with a different key, an exception is raised to protect against accidental removal of the key that’s currently associated with val.

Use put() instead if you want to specify different behavior in the case that the provided key or value duplicates an existing one. Or use forceput() to unconditionally associate key with val, replacing any existing items as necessary to preserve uniqueness.

Raises
Parameters
  • key (bidict._typing.KT) –

  • val (bidict._typing.VT) –

Return type

None

__slots__ = ()#
classmethod __subclasshook__(C)#

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

__weakref__#

list of weak references to the object (if defined)

clear()#

Remove all items.

Return type

None

copy()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

equals_order_sensitive(other)#

Order-sensitive equality check.

See also __eq__() is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive)

Parameters

other (object) –

Return type

bool

forceput(key, val)#

Associate key with val unconditionally.

Replace any existing mappings containing key key or value val as necessary to preserve uniqueness.

Parameters
  • key (bidict._typing.KT) –

  • val (bidict._typing.VT) –

Return type

None

forceupdate(*args, **kw)#

Like a bulk forceput().

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

get(k[, d]) D[k] if k in D, else d.  d defaults to None.#
property inv: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

Alias for inverse.

property inverse: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

The inverse of this bidirectional mapping instance.

items()#

A set-like object providing a view on the contained items.

When b._fwdm is a dict, b.items() returns a dict_items object that behaves exactly the same as collections.abc.ItemsView(b), except for:

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

ItemsView[bidict._typing.KT, bidict._typing.VT]

keys()#

A set-like object providing a view on the contained keys.

When b._fwdm is a dict, b.keys() returns a dict_keys object that behaves exactly the same as collections.abc.KeysView(b), except for

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

KeysView[bidict._typing.KT]

on_dup = OnDup(key=OD.DROP_OLD, val=OD.RAISE, kv=OD.RAISE)#
pop(key, default=MissingT.MISSING)#

x.pop(k[, d]) → v

Remove specified key and return the corresponding value.

Raises

KeyError – if key is not found and no default is provided.

Parameters
  • key (bidict._typing.KT) –

  • default (Union[bidict._typing.DT, bidict._typing.MissingT]) –

Return type

Union[bidict._typing.VT, bidict._typing.DT]

popitem()#

x.popitem() → (k, v)

Remove and return some item as a (key, value) pair.

Raises

KeyError – if x is empty.

Return type

Tuple[bidict._typing.KT, bidict._typing.VT]

put(key, val, on_dup=OnDup(key=OD.RAISE, val=OD.RAISE, kv=OD.RAISE))#

Associate key with val, honoring the OnDup given in on_dup.

For example, if on_dup is ON_DUP_RAISE, then key will be associated with val if and only if key is not already associated with an existing value and val is not already associated with an existing key, otherwise an exception will be raised.

If key is already associated with val, this is a no-op.

Raises
Parameters
  • key (bidict._typing.KT) –

  • val (bidict._typing.VT) –

  • on_dup (bidict.OnDup) –

Return type

None

putall(items, on_dup=OnDup(key=OD.RAISE, val=OD.RAISE, kv=OD.RAISE))#

Like a bulk put().

If one of the given items causes an exception to be raised, none of the items is inserted.

Parameters
Return type

None

setdefault(k[, d]) D.get(k,d), also set D[k]=d if k not in D#
update(*args, **kw)#

Like calling putall() with self.on_dup passed for on_dup.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

values()#

A set-like object providing a view on the contained values.

Since the values of a bidict are equivalent to the keys of its inverse, this method returns a set-like object for this bidict’s values rather than just a collections.abc.ValuesView. This object supports set operations like union and difference, and constant- rather than linear-time containment checks, and is no more expensive to provide than the less capable collections.abc.ValuesView would be.

See keys() for more information.

Return type

bidict.BidictKeysView[bidict._typing.VT]

class bidict.frozenbidict(*args, **kw)#

Bases: bidict.BidictBase[bidict._typing.KT, bidict._typing.VT]

Immutable, hashable bidict type.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

__abstractmethods__ = frozenset({})#
__annotations__ = {'_hash': <class 'int'>}#
__class_getitem__ = <bound method GenericAlias of <class 'bidict.frozenbidict'>>#
__contains__(key)#

True if the mapping contains the specified key, else False.

Parameters

key (Any) –

Return type

bool

__copy__()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

__dict__ = mappingproxy({'__module__': 'bidict', '__annotations__': {'_hash': <class 'int'>}, '__doc__': 'Immutable, hashable bidict type.', '__hash__': <function frozenbidict.__hash__>, '__orig_bases__': (bidict.BidictBase[~KT, ~VT],), '__parameters__': (~KT, ~VT), '_inv_cls': <class 'bidict.frozenbidict'>, '__reversed__': <function _fwdm_reversed>, '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data object>})#
__eq__(other)#

x.__eq__(other) ⟺ x == other

Equivalent to dict(x.items()) == dict(other.items()) but more efficient.

Note that bidict's __eq__() implementation is inherited by subclasses, in particular by the ordered bidict subclasses, so even with ordered bidicts, == comparison is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive).

See also equals_order_sensitive()

Parameters

other (object) –

Return type

bool

__getitem__(key)#

x.__getitem__(key) ⟺ x[key]

Parameters

key (bidict._typing.KT) –

Return type

bidict._typing.VT

__hash__()[source]#

The hash of this bidict as determined by its items.

Return type

int

__init__(*args, **kw)#

Make a new bidirectional mapping. The signature behaves like that of dict. Items passed in are added in the order they are passed, respecting the on_dup class attribute in the process.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

classmethod __init_subclass__()#

This method is called when a class is subclassed.

The default implementation does nothing. It may be overridden to extend subclasses.

Return type

None

__inverted__()#

Get an iterator over the items in inverse.

This is functionally equivalent to iterating over the items in the forward mapping and inverting each one on the fly, but this provides a more efficient implementation: Assuming the already-inverted items are stored in inverse, just return an iterator over them directly.

Providing this default implementation enables external functions, particularly inverted(), to use this optimized implementation when available, instead of having to invert on the fly.

See also bidict.inverted()

Return type

Iterator[Tuple[bidict._typing.VT, bidict._typing.KT]]

__iter__()#

Iterator over the contained keys.

Return type

Iterator[bidict._typing.KT]

__len__()#

The number of contained items.

Return type

int

__module__ = 'bidict'#
__or__(other)#

Return self|other.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__orig_bases__ = (bidict.BidictBase[~KT, ~VT],)#
__parameters__ = (~KT, ~VT)#
__reduce__()#

Return state information for pickling.

Return type

Tuple[Any, …]

__repr__()#

See repr().

Return type

str

__reversed__()#

Iterator over the contained keys in reverse order.

Parameters

self (bidict.BidictBase[bidict._typing.KT, Any]) –

Return type

Iterator[bidict._typing.KT]

__ror__(other)#

Return other|self.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__slots__ = ()#
classmethod __subclasshook__(C)#

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

__weakref__#

list of weak references to the object (if defined)

copy()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

equals_order_sensitive(other)#

Order-sensitive equality check.

See also __eq__() is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive)

Parameters

other (object) –

Return type

bool

get(k[, d]) D[k] if k in D, else d.  d defaults to None.#
property inv: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

Alias for inverse.

property inverse: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

The inverse of this bidirectional mapping instance.

items()#

A set-like object providing a view on the contained items.

When b._fwdm is a dict, b.items() returns a dict_items object that behaves exactly the same as collections.abc.ItemsView(b), except for:

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

ItemsView[bidict._typing.KT, bidict._typing.VT]

keys()#

A set-like object providing a view on the contained keys.

When b._fwdm is a dict, b.keys() returns a dict_keys object that behaves exactly the same as collections.abc.KeysView(b), except for

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

KeysView[bidict._typing.KT]

on_dup = OnDup(key=OD.DROP_OLD, val=OD.RAISE, kv=OD.RAISE)#
values()#

A set-like object providing a view on the contained values.

Since the values of a bidict are equivalent to the keys of its inverse, this method returns a set-like object for this bidict’s values rather than just a collections.abc.ValuesView. This object supports set operations like union and difference, and constant- rather than linear-time containment checks, and is no more expensive to provide than the less capable collections.abc.ValuesView would be.

See keys() for more information.

Return type

bidict.BidictKeysView[bidict._typing.VT]

class bidict.FrozenOrderedBidict(*args, **kw)#

Bases: bidict.OrderedBidictBase[bidict._typing.KT, bidict._typing.VT]

Hashable, immutable, ordered bidict type.

Like a hashable bidict.OrderedBidict without the mutating APIs, or like a reversible bidict.frozenbidict even on Python < 3.8. (All bidicts are order-preserving when never mutated, so frozenbidict is already order-preserving, but only on Python 3.8+, where dicts are reversible, are all bidicts (including frozenbidict) also reversible.)

If you are using Python 3.8+, frozenbidict gives you everything that FrozenOrderedBidict gives you, but with less space overhead. On the other hand, using FrozenOrderedBidict when you are depending on the ordering of the items can make the ordering dependence more explicit.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

__abstractmethods__ = frozenset({})#
__annotations__ = {'__hash__': typing.Callable[[typing.Any], int]}#
__class_getitem__ = <bound method GenericAlias of <class 'bidict.FrozenOrderedBidict'>>#
__contains__(key)#

True if the mapping contains the specified key, else False.

Parameters

key (Any) –

Return type

bool

__copy__()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

__dict__ = mappingproxy({'__module__': 'bidict', '__annotations__': {'__hash__': typing.Callable[[typing.Any], int]}, '__doc__': 'Hashable, immutable, ordered bidict type.\n\n    Like a hashable :class:`bidict.OrderedBidict`\n    without the mutating APIs, or like a\n    reversible :class:`bidict.frozenbidict` even on Python < 3.8.\n    (All bidicts are order-preserving when never mutated, so frozenbidict is\n    already order-preserving, but only on Python 3.8+, where dicts are\n    reversible, are all bidicts (including frozenbidict) also reversible.)\n\n    If you are using Python 3.8+, frozenbidict gives you everything that\n    FrozenOrderedBidict gives you, but with less space overhead.\n    On the other hand, using FrozenOrderedBidict when you are depending on\n    the ordering of the items can make the ordering dependence more explicit.\n    ', '__hash__': <function frozenbidict.__hash__>, '__orig_bases__': (bidict.OrderedBidictBase[~KT, ~VT],), '__parameters__': (~KT, ~VT), '_inv_cls': <class 'bidict.FrozenOrderedBidict'>, '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data object>})#
__eq__(other)#

x.__eq__(other) ⟺ x == other

Equivalent to dict(x.items()) == dict(other.items()) but more efficient.

Note that bidict's __eq__() implementation is inherited by subclasses, in particular by the ordered bidict subclasses, so even with ordered bidicts, == comparison is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive).

See also equals_order_sensitive()

Parameters

other (object) –

Return type

bool

__getitem__(key)#

x.__getitem__(key) ⟺ x[key]

Parameters

key (bidict._typing.KT) –

Return type

bidict._typing.VT

__hash__()#

The hash of this bidict as determined by its items.

Return type

int

__init__(*args, **kw)#

Make a new ordered bidirectional mapping. The signature behaves like that of dict. Items passed in are added in the order they are passed, respecting the on_dup class attribute in the process.

The order in which items are inserted is remembered, similar to collections.OrderedDict.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

classmethod __init_subclass__()#

This method is called when a class is subclassed.

The default implementation does nothing. It may be overridden to extend subclasses.

Return type

None

__inverted__()#

Get an iterator over the items in inverse.

This is functionally equivalent to iterating over the items in the forward mapping and inverting each one on the fly, but this provides a more efficient implementation: Assuming the already-inverted items are stored in inverse, just return an iterator over them directly.

Providing this default implementation enables external functions, particularly inverted(), to use this optimized implementation when available, instead of having to invert on the fly.

See also bidict.inverted()

Return type

Iterator[Tuple[bidict._typing.VT, bidict._typing.KT]]

__iter__()#

Iterator over the contained keys in insertion order.

Return type

Iterator[bidict._typing.KT]

__len__()#

The number of contained items.

Return type

int

__module__ = 'bidict'#
__or__(other)#

Return self|other.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__orig_bases__ = (bidict.OrderedBidictBase[~KT, ~VT],)#
__parameters__ = (~KT, ~VT)#
__reduce__()#

Return state information for pickling.

Return type

Tuple[Any, …]

__repr__()#

See repr().

Return type

str

__reversed__()#

Iterator over the contained keys in reverse insertion order.

Parameters

self (bidict.OrderedBidictBase[bidict._typing.KT, bidict._typing.VT]) –

Return type

Iterator[bidict._typing.KT]

__ror__(other)#

Return other|self.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__slots__ = ()#
classmethod __subclasshook__(C)#

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

__weakref__#

list of weak references to the object (if defined)

copy()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

equals_order_sensitive(other)#

Order-sensitive equality check.

See also __eq__() is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive)

Parameters

other (object) –

Return type

bool

get(k[, d]) D[k] if k in D, else d.  d defaults to None.#
property inv: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

Alias for inverse.

property inverse: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

The inverse of this bidirectional mapping instance.

items()#

A set-like object providing a view on the contained items.

When b._fwdm is a dict, b.items() returns a dict_items object that behaves exactly the same as collections.abc.ItemsView(b), except for:

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

ItemsView[bidict._typing.KT, bidict._typing.VT]

keys()#

A set-like object providing a view on the contained keys.

When b._fwdm is a dict, b.keys() returns a dict_keys object that behaves exactly the same as collections.abc.KeysView(b), except for

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

KeysView[bidict._typing.KT]

on_dup = OnDup(key=OD.DROP_OLD, val=OD.RAISE, kv=OD.RAISE)#
values()#

A set-like object providing a view on the contained values.

Since the values of a bidict are equivalent to the keys of its inverse, this method returns a set-like object for this bidict’s values rather than just a collections.abc.ValuesView. This object supports set operations like union and difference, and constant- rather than linear-time containment checks, and is no more expensive to provide than the less capable collections.abc.ValuesView would be.

See keys() for more information.

Return type

bidict.BidictKeysView[bidict._typing.VT]

class bidict.NamedBidictBase#

Bases: object

Base class that namedbidicts derive from.

__dict__ = mappingproxy({'__module__': 'bidict', '__doc__': 'Base class that namedbidicts derive from.', '__dict__': <attribute '__dict__' of 'NamedBidictBase' objects>, '__weakref__': <attribute '__weakref__' of 'NamedBidictBase' objects>, '__annotations__': {}})#
__module__ = 'bidict'#
__weakref__#

list of weak references to the object (if defined)

bidict.namedbidict(typename, keyname, valname, *, base_type=<class 'bidict.bidict'>)#

Create a new subclass of base_type with custom accessors.

Like collections.namedtuple() for bidicts.

The new class’s __name__ and __qualname__ will be set to typename, and its __module__ will be set to the caller’s module.

Instances of the new class will provide access to their inverse instances via the custom keyname_for property, and access to themselves via the custom valname_for property.

See also the namedbidict usage documentation (https://bidict.rtfd.io/other-bidict-types.html#namedbidict)

Raises
Parameters
Return type

Type[bidict.BidictBase[bidict._typing.KT, bidict._typing.VT]]

class bidict.OrderedBidictBase(*args, **kw)#

Bases: bidict.BidictBase[bidict._typing.KT, bidict._typing.VT]

Base class implementing an ordered BidirectionalMapping.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

__abstractmethods__ = frozenset({})#
__annotations__ = {'_bykey': <class 'bool'>, '_node_by_korv': bidict.bidict[typing.Any, bidict._orderedbase.Node], '_repr_delegate': typing.ClassVar[typing.Any]}#
__class_getitem__ = <bound method GenericAlias of <class 'bidict.OrderedBidictBase'>>#
__contains__(key)#

True if the mapping contains the specified key, else False.

Parameters

key (Any) –

Return type

bool

__copy__()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

__dict__ = mappingproxy({'__module__': 'bidict', '__annotations__': {'_repr_delegate': typing.ClassVar[typing.Any], '_node_by_korv': bidict.bidict[typing.Any, bidict._orderedbase.Node], '_bykey': <class 'bool'>}, '__doc__': 'Base class implementing an ordered :class:`BidirectionalMapping`.', '_repr_delegate': <class 'list'>, '__init__': <function OrderedBidictBase.__init__>, '_make_inverse': <function OrderedBidictBase._make_inverse>, '_assoc_node': <function OrderedBidictBase._assoc_node>, '_dissoc_node': <function OrderedBidictBase._dissoc_node>, '_init_from': <function OrderedBidictBase._init_from>, '_prep_write': <function OrderedBidictBase._prep_write>, '__iter__': <function OrderedBidictBase.__iter__>, '__reversed__': <function OrderedBidictBase.__reversed__>, '_iter': <function OrderedBidictBase._iter>, '__orig_bases__': (bidict.BidictBase[~KT, ~VT],), '__parameters__': (~KT, ~VT), '_inv_cls': <class 'bidict.OrderedBidictBase'>, '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data object>})#
__eq__(other)#

x.__eq__(other) ⟺ x == other

Equivalent to dict(x.items()) == dict(other.items()) but more efficient.

Note that bidict's __eq__() implementation is inherited by subclasses, in particular by the ordered bidict subclasses, so even with ordered bidicts, == comparison is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive).

See also equals_order_sensitive()

Parameters

other (object) –

Return type

bool

__getitem__(key)#

x.__getitem__(key) ⟺ x[key]

Parameters

key (bidict._typing.KT) –

Return type

bidict._typing.VT

__hash__ = None#
__init__(*args, **kw)[source]#

Make a new ordered bidirectional mapping. The signature behaves like that of dict. Items passed in are added in the order they are passed, respecting the on_dup class attribute in the process.

The order in which items are inserted is remembered, similar to collections.OrderedDict.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

classmethod __init_subclass__()#

This method is called when a class is subclassed.

The default implementation does nothing. It may be overridden to extend subclasses.

Return type

None

__inverted__()#

Get an iterator over the items in inverse.

This is functionally equivalent to iterating over the items in the forward mapping and inverting each one on the fly, but this provides a more efficient implementation: Assuming the already-inverted items are stored in inverse, just return an iterator over them directly.

Providing this default implementation enables external functions, particularly inverted(), to use this optimized implementation when available, instead of having to invert on the fly.

See also bidict.inverted()

Return type

Iterator[Tuple[bidict._typing.VT, bidict._typing.KT]]

__iter__()[source]#

Iterator over the contained keys in insertion order.

Return type

Iterator[bidict._typing.KT]

__len__()#

The number of contained items.

Return type

int

__module__ = 'bidict'#
__or__(other)#

Return self|other.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__orig_bases__ = (bidict.BidictBase[~KT, ~VT],)#
__parameters__ = (~KT, ~VT)#
__reduce__()#

Return state information for pickling.

Return type

Tuple[Any, …]

__repr__()#

See repr().

Return type

str

__reversed__()[source]#

Iterator over the contained keys in reverse insertion order.

Parameters

self (bidict.OrderedBidictBase[bidict._typing.KT, bidict._typing.VT]) –

Return type

Iterator[bidict._typing.KT]

__ror__(other)#

Return other|self.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__slots__ = ()#
classmethod __subclasshook__(C)#

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

__weakref__#

list of weak references to the object (if defined)

copy()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

equals_order_sensitive(other)#

Order-sensitive equality check.

See also __eq__() is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive)

Parameters

other (object) –

Return type

bool

get(k[, d]) D[k] if k in D, else d.  d defaults to None.#
property inv: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

Alias for inverse.

property inverse: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

The inverse of this bidirectional mapping instance.

items()#

A set-like object providing a view on the contained items.

When b._fwdm is a dict, b.items() returns a dict_items object that behaves exactly the same as collections.abc.ItemsView(b), except for:

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

ItemsView[bidict._typing.KT, bidict._typing.VT]

keys()#

A set-like object providing a view on the contained keys.

When b._fwdm is a dict, b.keys() returns a dict_keys object that behaves exactly the same as collections.abc.KeysView(b), except for

  • offering better performance

  • being reversible on Python 3.8+

  • having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to b._fwdm.

Return type

KeysView[bidict._typing.KT]

on_dup = OnDup(key=OD.DROP_OLD, val=OD.RAISE, kv=OD.RAISE)#
values()#

A set-like object providing a view on the contained values.

Since the values of a bidict are equivalent to the keys of its inverse, this method returns a set-like object for this bidict’s values rather than just a collections.abc.ValuesView. This object supports set operations like union and difference, and constant- rather than linear-time containment checks, and is no more expensive to provide than the less capable collections.abc.ValuesView would be.

See keys() for more information.

Return type

bidict.BidictKeysView[bidict._typing.VT]

class bidict.OrderedBidict(*args, **kw)#

Bases: bidict.OrderedBidictBase[bidict._typing.KT, bidict._typing.VT], bidict.MutableBidict[bidict._typing.KT, bidict._typing.VT]

Mutable bidict type that maintains items in insertion order.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

__abstractmethods__ = frozenset({})#
__annotations__ = {}#
__class_getitem__ = <bound method GenericAlias of <class 'bidict.OrderedBidict'>>#
__contains__(key)#

True if the mapping contains the specified key, else False.

Parameters

key (Any) –

Return type

bool

__copy__()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

__delitem__(key)#

x.__delitem__(y) ⟺ del x[y]

Parameters

key (bidict._typing.KT) –

Return type

None

__dict__ = mappingproxy({'__module__': 'bidict', '__doc__': 'Mutable bidict type that maintains items in insertion order.', 'clear': <function OrderedBidict.clear>, '_pop': <function OrderedBidict._pop>, 'popitem': <function OrderedBidict.popitem>, 'move_to_end': <function OrderedBidict.move_to_end>, 'keys': <function OrderedBidict.keys>, 'items': <function OrderedBidict.items>, '__orig_bases__': (bidict.OrderedBidictBase[~KT, ~VT], bidict.MutableBidict[~KT, ~VT]), '__parameters__': (~KT, ~VT), '_inv_cls': <class 'bidict.OrderedBidict'>, '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data object>, '__annotations__': {}})#
__eq__(other)#

x.__eq__(other) ⟺ x == other

Equivalent to dict(x.items()) == dict(other.items()) but more efficient.

Note that bidict's __eq__() implementation is inherited by subclasses, in particular by the ordered bidict subclasses, so even with ordered bidicts, == comparison is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive).

See also equals_order_sensitive()

Parameters

other (object) –

Return type

bool

__getitem__(key)#

x.__getitem__(key) ⟺ x[key]

Parameters

key (bidict._typing.KT) –

Return type

bidict._typing.VT

__hash__ = None#
__init__(*args, **kw)#

Make a new ordered bidirectional mapping. The signature behaves like that of dict. Items passed in are added in the order they are passed, respecting the on_dup class attribute in the process.

The order in which items are inserted is remembered, similar to collections.OrderedDict.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

classmethod __init_subclass__()#

This method is called when a class is subclassed.

The default implementation does nothing. It may be overridden to extend subclasses.

Return type

None

__inverted__()#

Get an iterator over the items in inverse.

This is functionally equivalent to iterating over the items in the forward mapping and inverting each one on the fly, but this provides a more efficient implementation: Assuming the already-inverted items are stored in inverse, just return an iterator over them directly.

Providing this default implementation enables external functions, particularly inverted(), to use this optimized implementation when available, instead of having to invert on the fly.

See also bidict.inverted()

Return type

Iterator[Tuple[bidict._typing.VT, bidict._typing.KT]]

__ior__(other)#

Return self|=other.

Parameters

other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict.MutableBidict[bidict._typing.KT, bidict._typing.VT]

__iter__()#

Iterator over the contained keys in insertion order.

Return type

Iterator[bidict._typing.KT]

__len__()#

The number of contained items.

Return type

int

__module__ = 'bidict'#
__or__(other)#

Return self|other.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__orig_bases__ = (bidict.OrderedBidictBase[~KT, ~VT], bidict.MutableBidict[~KT, ~VT])#
__parameters__ = (~KT, ~VT)#
__reduce__()#

Return state information for pickling.

Return type

Tuple[Any, …]

__repr__()#

See repr().

Return type

str

__reversed__()#

Iterator over the contained keys in reverse insertion order.

Parameters

self (bidict.OrderedBidictBase[bidict._typing.KT, bidict._typing.VT]) –

Return type

Iterator[bidict._typing.KT]

__ror__(other)#

Return other|self.

Parameters
  • self (bidict._base.BT) –

  • other (Mapping[bidict._typing.KT, bidict._typing.VT]) –

Return type

bidict._base.BT

__setitem__(key, val)#

Set the value for key to val.

If key is already associated with val, this is a no-op.

If key is already associated with a different value, the old value will be replaced with val, as with dict’s __setitem__().

If val is already associated with a different key, an exception is raised to protect against accidental removal of the key that’s currently associated with val.

Use put() instead if you want to specify different behavior in the case that the provided key or value duplicates an existing one. Or use forceput() to unconditionally associate key with val, replacing any existing items as necessary to preserve uniqueness.

Raises
Parameters
  • key (bidict._typing.KT) –

  • val (bidict._typing.VT) –

Return type

None

__slots__ = ()#
classmethod __subclasshook__(C)#

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

__weakref__#

list of weak references to the object (if defined)

clear()[source]#

Remove all items.

Return type

None

copy()#

Make a (shallow) copy of this bidict.

Parameters

self (bidict._base.BT) –

Return type

bidict._base.BT

equals_order_sensitive(other)#

Order-sensitive equality check.

See also __eq__() is order-insensitive (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive)

Parameters

other (object) –

Return type

bool

forceput(key, val)#

Associate key with val unconditionally.

Replace any existing mappings containing key key or value val as necessary to preserve uniqueness.

Parameters
  • key (bidict._typing.KT) –

  • val (bidict._typing.VT) –

Return type

None

forceupdate(*args, **kw)#

Like a bulk forceput().

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

get(k[, d]) D[k] if k in D, else d.  d defaults to None.#
property inv: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

Alias for inverse.

property inverse: bidict.BidictBase[bidict._typing.VT, bidict._typing.KT]#

The inverse of this bidirectional mapping instance.

items()[source]#

A set-like object providing a view on the contained items.

Return type

ItemsView[bidict._typing.KT, bidict._typing.VT]

keys()[source]#

A set-like object providing a view on the contained keys.

Return type

KeysView[bidict._typing.KT]

move_to_end(key, last=True)[source]#

Move the item with the given key to the end if last is true, else to the beginning.

Raises

KeyError – if key is missing

Parameters
  • key (bidict._typing.KT) –

  • last (bool) –

Return type

None

on_dup = OnDup(key=OD.DROP_OLD, val=OD.RAISE, kv=OD.RAISE)#
pop(key, default=MissingT.MISSING)#

x.pop(k[, d]) → v

Remove specified key and return the corresponding value.

Raises

KeyError – if key is not found and no default is provided.

Parameters
  • key (bidict._typing.KT) –

  • default (Union[bidict._typing.DT, bidict._typing.MissingT]) –

Return type

Union[bidict._typing.VT, bidict._typing.DT]

popitem(last=True)[source]#

b.popitem() → (k, v)

If last is true, remove and return the most recently added item as a (key, value) pair. Otherwise, remove and return the least recently added item.

Raises

KeyError – if b is empty.

Parameters

last (bool) –

Return type

Tuple[bidict._typing.KT, bidict._typing.VT]

put(key, val, on_dup=OnDup(key=OD.RAISE, val=OD.RAISE, kv=OD.RAISE))#

Associate key with val, honoring the OnDup given in on_dup.

For example, if on_dup is ON_DUP_RAISE, then key will be associated with val if and only if key is not already associated with an existing value and val is not already associated with an existing key, otherwise an exception will be raised.

If key is already associated with val, this is a no-op.

Raises
Parameters
  • key (bidict._typing.KT) –

  • val (bidict._typing.VT) –

  • on_dup (bidict.OnDup) –

Return type

None

putall(items, on_dup=OnDup(key=OD.RAISE, val=OD.RAISE, kv=OD.RAISE))#

Like a bulk put().

If one of the given items causes an exception to be raised, none of the items is inserted.

Parameters
Return type

None

setdefault(k[, d]) D.get(k,d), also set D[k]=d if k not in D#
update(*args, **kw)#

Like calling putall() with self.on_dup passed for on_dup.

Parameters
  • args (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

  • kw (bidict._typing.VT) –

Return type

None

values()#

A set-like object providing a view on the contained values.

Since the values of a bidict are equivalent to the keys of its inverse, this method returns a set-like object for this bidict’s values rather than just a collections.abc.ValuesView. This object supports set operations like union and difference, and constant- rather than linear-time containment checks, and is no more expensive to provide than the less capable collections.abc.ValuesView would be.

See keys() for more information.

Return type

bidict.BidictKeysView[bidict._typing.VT]

class bidict.OnDup(key=OD.DROP_OLD, val=OD.RAISE, kv=None)#

Bases: bidict._dup._OnDup

A 3-tuple of ODs specifying how to handle the 3 kinds of duplication.

See also Values Must Be Unique (https://bidict.rtfd.io/basic-usage.html#values-must-be-unique)

If kv is not specified, val will be used for kv.

Parameters
Return type

OnDup

__add__(value, /)#

Return self+value.

__annotations__ = {}#
__class_getitem__()#

See PEP 585

__contains__(key, /)#

Return key in self.

__eq__(value, /)#

Return self==value.

__ge__(value, /)#

Return self>=value.

__getattribute__(name, /)#

Return getattr(self, name).

__getitem__(key, /)#

Return self[key].

__getnewargs__()#

Return self as a plain tuple. Used by copy and pickle.

__gt__(value, /)#

Return self>value.

__hash__()#

Return hash(self).

__iter__()#

Implement iter(self).

__le__(value, /)#

Return self<=value.

__len__()#

Return len(self).

__lt__(value, /)#

Return self<value.

__match_args__ = ('key', 'val', 'kv')#
__module__ = 'bidict'#
__mul__(value, /)#

Return self*value.

__ne__(value, /)#

Return self!=value.

static __new__(cls, key=OD.DROP_OLD, val=OD.RAISE, kv=None)[source]#

Override to provide user-friendly default values.

Parameters
Return type

bidict.OnDup

__repr__()#

Return a nicely formatted representation string

__rmul__(value, /)#

Return value*self.

__slots__ = ()#
count(value, /)#

Return number of occurrences of value.

index(value, start=0, stop=9223372036854775807, /)#

Return first index of value.

Raises ValueError if the value is not present.

key: bidict.OD#

Alias for field number 0

kv: bidict.OD#

Alias for field number 2

val: bidict.OD#

Alias for field number 1

class bidict.OD(value)#

Bases: enum.Enum

An action to take to prevent duplication from occurring.

RAISE = 'RAISE'#
DROP_OLD = 'DROP_OLD'#
DROP_NEW = 'DROP_NEW'#
__module__ = 'bidict'#
exception bidict.BidictException#

Bases: Exception

Base class for bidict exceptions.

__cause__#

exception cause

__context__#

exception context

__delattr__(name, /)#

Implement delattr(self, name).

__dict__ = mappingproxy({'__module__': 'bidict', '__doc__': 'Base class for bidict exceptions.', '__weakref__': <attribute '__weakref__' of 'BidictException' objects>, '__annotations__': {}})#
__getattribute__(name, /)#

Return getattr(self, name).

__init__(*args, **kwargs)#
__module__ = 'bidict'#
__new__(**kwargs)#
__reduce__()#

Helper for pickle.

__repr__()#

Return repr(self).

__setattr__(name, value, /)#

Implement setattr(self, name, value).

__setstate__()#
__str__()#

Return str(self).

__suppress_context__#
__traceback__#
__weakref__#

list of weak references to the object (if defined)

args#
with_traceback()#

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception bidict.DuplicationError#

Bases: bidict.BidictException

Base class for exceptions raised when uniqueness is violated as per the :attr:~bidict.RAISE` OnDupAction.

__annotations__ = {}#
__cause__#

exception cause

__context__#

exception context

__delattr__(name, /)#

Implement delattr(self, name).

__dict__ = mappingproxy({'__module__': 'bidict', '__doc__': 'Base class for exceptions raised when uniqueness is violated\n    as per the :attr:~bidict.RAISE` :class:`~bidict.OnDupAction`.\n    ', '__annotations__': {}})#
__getattribute__(name, /)#

Return getattr(self, name).

__init__(*args, **kwargs)#
__module__ = 'bidict'#
__new__(**kwargs)#
__reduce__()#

Helper for pickle.

__repr__()#

Return repr(self).

__setattr__(name, value, /)#

Implement setattr(self, name, value).

__setstate__()#
__str__()#

Return str(self).

__suppress_context__#
__traceback__#
__weakref__#

list of weak references to the object (if defined)

args#
with_traceback()#

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception bidict.KeyDuplicationError#

Bases: bidict.DuplicationError

Raised when a given key is not unique.

__annotations__ = {}#
__cause__#

exception cause

__context__#

exception context

__delattr__(name, /)#

Implement delattr(self, name).

__dict__ = mappingproxy({'__module__': 'bidict', '__doc__': 'Raised when a given key is not unique.', '__annotations__': {}})#
__getattribute__(name, /)#

Return getattr(self, name).

__init__(*args, **kwargs)#
__module__ = 'bidict'#
__new__(**kwargs)#
__reduce__()#

Helper for pickle.

__repr__()#

Return repr(self).

__setattr__(name, value, /)#

Implement setattr(self, name, value).

__setstate__()#
__str__()#

Return str(self).

__suppress_context__#
__traceback__#
__weakref__#

list of weak references to the object (if defined)

args#
with_traceback()#

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception bidict.ValueDuplicationError#

Bases: bidict.DuplicationError

Raised when a given value is not unique.

__annotations__ = {}#
__cause__#

exception cause

__context__#

exception context

__delattr__(name, /)#

Implement delattr(self, name).

__dict__ = mappingproxy({'__module__': 'bidict', '__doc__': 'Raised when a given value is not unique.', '__annotations__': {}})#
__getattribute__(name, /)#

Return getattr(self, name).

__init__(*args, **kwargs)#
__module__ = 'bidict'#
__new__(**kwargs)#
__reduce__()#

Helper for pickle.

__repr__()#

Return repr(self).

__setattr__(name, value, /)#

Implement setattr(self, name, value).

__setstate__()#
__str__()#

Return str(self).

__suppress_context__#
__traceback__#
__weakref__#

list of weak references to the object (if defined)

args#
with_traceback()#

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception bidict.KeyAndValueDuplicationError#

Bases: bidict.KeyDuplicationError, bidict.ValueDuplicationError

Raised when a given item’s key and value are not unique.

That is, its key duplicates that of another item, and its value duplicates that of a different other item.

__annotations__ = {}#
__cause__#

exception cause

__context__#

exception context

__delattr__(name, /)#

Implement delattr(self, name).

__dict__ = mappingproxy({'__module__': 'bidict', '__doc__': "Raised when a given item's key and value are not unique.\n\n    That is, its key duplicates that of another item,\n    and its value duplicates that of a different other item.\n    ", '__annotations__': {}})#
__getattribute__(name, /)#

Return getattr(self, name).

__init__(*args, **kwargs)#
__module__ = 'bidict'#
__new__(**kwargs)#
__reduce__()#

Helper for pickle.

__repr__()#

Return repr(self).

__setattr__(name, value, /)#

Implement setattr(self, name, value).

__setstate__()#
__str__()#

Return str(self).

__suppress_context__#
__traceback__#
__weakref__#

list of weak references to the object (if defined)

args#
with_traceback()#

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

bidict.inverted(arg)#

Yield the inverse items of the provided object.

If arg has a callable() __inverted__ attribute, return the result of calling it.

Otherwise, return an iterator over the items in arg, inverting each item on the fly.

See also bidict.BidirectionalMapping.__inverted__

Parameters

arg (Union[Mapping[bidict._typing.KT, bidict._typing.VT], Iterable[Tuple[bidict._typing.KT, bidict._typing.VT]]]) –

Return type

Iterable[Tuple[bidict._typing.VT, bidict._typing.KT]]

bidict.OnDupAction#

Alias

bidict.RAISE = OD.RAISE#

An action to take to prevent duplication from occurring.

bidict.DROP_OLD = OD.DROP_OLD#

An action to take to prevent duplication from occurring.

bidict.DROP_NEW = OD.DROP_NEW#

An action to take to prevent duplication from occurring.

bidict.ON_DUP_DEFAULT = OnDup(key=OD.DROP_OLD, val=OD.RAISE, kv=OD.RAISE)#

A 3-tuple of ODs specifying how to handle the 3 kinds of duplication.

See also Values Must Be Unique (https://bidict.rtfd.io/basic-usage.html#values-must-be-unique)

If kv is not specified, val will be used for kv.

bidict.ON_DUP_RAISE = OnDup(key=OD.RAISE, val=OD.RAISE, kv=OD.RAISE)#

A 3-tuple of ODs specifying how to handle the 3 kinds of duplication.

See also Values Must Be Unique (https://bidict.rtfd.io/basic-usage.html#values-must-be-unique)

If kv is not specified, val will be used for kv.

bidict.ON_DUP_DROP_OLD = OnDup(key=OD.DROP_OLD, val=OD.DROP_OLD, kv=OD.DROP_OLD)#

A 3-tuple of ODs specifying how to handle the 3 kinds of duplication.

See also Values Must Be Unique (https://bidict.rtfd.io/basic-usage.html#values-must-be-unique)

If kv is not specified, val will be used for kv.

bidict.__version__#

The version of bidict represented as a string.

Learning from bidict#

Working on bidict has taken me to some of the most interesting and unexpected places I’ve ever gotten to visit in many years of programming. (When I started this project almost 15 years ago, I’d never heard of things like higher-kinded types. Thanks to bidict, I not only came across them, I even got to share an example with Guido of how they might be useful for bidirectional mapping types.)

The problem space that bidict inhabits is abundant with beautiful symmetries, delightful surprises, and rich opportunities to come up with elegant solutions.

You can check out bidict’s source to see for yourself. I’ve sought to optimize the code not just for correctness and performance, but also for clarity, maintainability, and to make for an enjoyable read.

See below for more, and feel free to let me know what you think. I hope reading bidict’s code brings you some of the joy that bidict has brought me.

Code structure#

bidicts come in every combination of mutable, immutable, ordered, and unordered types, implementing Python’s various relevant collections interfaces as appropriate.

Factoring the code to maximize reuse, modularity, and adherence to SOLID design principles (while not missing any chances for specialized optimizations) has been one of the most fun parts of working on bidict.

To see how this is done, check out the code starting with __init__.py, and then follow the path suggested in the “code review nav” comments at the top of the file:

Data structures are amazing#

Data structures are one of the most fascinating and important building blocks of programming and computer science.

It’s all too easy to lose sight of the magic when having to implement them for computer science courses or job interview questions. Part of this is because many of the most interesting real-world details get left out, and you miss all the value that comes from ongoing, direct practical application.

Bidict shows how fundamental data structures can be implemented in Python for important real-world usage, with practical concerns at top of mind.

To give you a taste…

A regular bidict encapsulates two regular dicts, keeping them in sync to preserve the bidirectional mapping invariants. Since dicts are unordered, regular bidicts are unordered too. How should we extend this to implement an ordered bidict?

OrderedBidictBase inherits from BidictBase the use of two regular dicts to store the forward and inverse associations. And to store the ordering of the associations, we use a doubly-linked list. This allows us to e.g. move any item to the front of the ordering in O(1) time.

Interestingly, the nodes of the linked list encode only the ordering of the items; the nodes themselves contain no key or value data. An additional backing mapping associates the key/value data with the nodes, providing the final piece of the puzzle.

And since OrderedBidictBase needs to not only look up nodes by key/value, but also key/value by node, it uses an (unordered) bidict for this internally. Bidicts all the way down!

Python syntax hacks#

bidict used to support (ab)using a specialized form of Python’s slice syntax for getting and setting keys by value:

>>> element_by_symbol = bidict(H='hydrogen')
>>> element_by_symbol['H']  # [normal] syntax for the forward mapping
'hydrogen'
>>> element_by_symbol[:'hydrogen']  # [:slice] syntax for the inverse (no longer supported)
'H'

See this code for how this was implemented, and #19 for why this was dropped.

Property-based testing is indispensable#

When your automated tests run, are they only checking the test cases you happened to hard-code into your test suite? How do you know these test cases aren’t missing some important edge cases?

With property-based testing, you describe the types of test case inputs your functions accept, along with the properties that should hold for all inputs. Rather than having to think up your test case inputs manually and hard-code them into your test suite, they get generated for you dynamically, in much greater quantity and edge case-exercising diversity than you could come up with by hand. This dramatically increases test coverage and confidence that your code is correct.

Bidict never would have survived so many refactorings with so few bugs if it weren’t for property-based testing, enabled by the amazing Hypothesis library.

Check out bidict’s property-based tests to see this in action.

Python surprises#

  • What should happen when checking equality of several ordered mappings that contain the same items but in a different order?

    What about when comparing an ordered mapping with an unordered mapping?

    First let’s see how collections.OrderedDict works. The results may surprise you:

    >>> from collections import OrderedDict
    >>> x = OrderedDict({1: 1, 2: 2})
    >>> y = {1: 1, 2: 2}
    >>> z = OrderedDict({2: 2, 1: 1})
    >>> x == y
    True
    >>> y == z
    True
    >>> x == z
    False
    

    So collections.OrderedDict violates the transitive property of equality. This can lead to some even more unusual behavior than the above. As an example, let’s see what would happen if bidict.FrozenOrderedBidict.__eq__ behaved this way:

    >>> class BadFrozenOrderedBidict(FrozenOrderedBidict):
    ...     __hash__ = FrozenOrderedBidict.__hash__
    ...
    ...     def __eq__(self, other):  # (deliberately simplified)
    ...         # Override to be order-sensitive, like collections.OrderedDict:
    ...         return all(i == j for (i, j) in zip(self.items(), other.items()))
    
    
    >>> x = BadFrozenOrderedBidict({1: 1, 2: 2})
    >>> y = frozenbidict({1: 1, 2: 2})
    >>> z = BadFrozenOrderedBidict({2: 2, 1: 1})
    >>> assert x == y and y == z and x != z
    >>> set1 = {x, y, z}
    >>> len(set1)
    2
    >>> set2 = {y, x, z}
    >>> len(set2)
    1
    

    Gotcha alert!

    According to Raymond Hettinger, the Python core developer who built Python’s collections foundation, if we had it to do over again, we would make collections.OrderedDict.__eq__() order-insensitive. Making __eq__ order-sensitive not only violates the transitive property of equality, but also the Liskov substitution principle. Unfortunately, it’s too late now to fix this for collections.OrderedDict.

    Fortunately though, it’s not too late for bidict to learn from this. Hence __eq__() is order-insensitive, even for ordered bidicts. For an order-sensitive equality check, bidict provides the separate equals_order_sensitive() method, thanks in no small part to Raymond’s good advice.

  • See nan as a Key.

  • See Equivalent but distinct Hashables.

Better memory usage through __slots__#

Using __slots__ speeds up attribute access, and can dramatically reduce memory usage in CPython when creating many instances of the same class.

As an example, the Node class used internally by OrderedBidictBase to store the ordering of inserted items uses slots for better performance at scale, since as many node instances are kept in memory as there are items in every ordered bidict in memory. See: _orderedbase.py

(Note that extra care must be taken when using slots with pickling and weakrefs.)

Better memory usage through weakref#

A bidict and its inverse use weakref to avoid creating a reference cycle. As a result, when you drop your last reference to a bidict, its memory is reclaimed immediately in CPython rather than having to wait for the next garbage collection. See: _base.py

As another example, the Node class used internally by OrderedBidictBase uses weakrefs to avoid creating reference cycles in the doubly-linked lists used to encode the ordering of inserted items. See: _orderedbase.py

Using descriptors for managed attributes#

To abstract the details of creating and dereferencing the weakrefs that OrderedBidictBase's aforementioned doubly-linked list nodes use to refer to their neighbor nodes, a WeakAttr descriptor is used to manage access to these attributes automatically. See: _orderedbase.py

The implicit __class__ reference#

Anytime you have to reference the exact class of an instance (and not a potential subclass) from within a method body, you can use the implicit, lexically-scoped __class__ reference rather than hard-coding the current class’s name. See: https://docs.python.org/3/reference/datamodel.html#executing-the-class-body

Subclassing namedtuple() classes#

To get the performance benefits, intrinsic sortability, etc. of namedtuple() while customizing behavior, state, API, etc., you can subclass a namedtuple() class. (Make sure to include __slots__ = (), if you want to keep the associated performance benefits – see the section about slots above.)

See the OnDup class in _dup.py for an example.

Here’s another example:

>>> from collections import namedtuple
>>> from itertools import count

>>> class Node(namedtuple('_Node', 'cost tiebreaker data parent depth')):
...     """Represent nodes in a graph traversal. Suitable for use with e.g. heapq."""
...
...     __slots__ = ()
...     _counter = count()  # break ties between equal-cost nodes, avoid comparing data
...
...     # Give call sites a cleaner API for creating new Nodes
...     def __new__(cls, cost, data, parent=None):
...         tiebreaker = next(cls._counter)
...         depth = parent.depth + 1 if parent else 0
...         return super().__new__(cls, cost, tiebreaker, data, parent, depth)
...
...     def __repr__(self):
...         return 'Node(cost={cost}, data={data!r})'.format(**self._asdict())

>>> start = Node(cost=0, data='foo')
>>> child = Node(cost=5, data='bar', parent=start)
>>> child
Node(cost=5, data='bar')
>>> child.parent
Node(cost=0, data='foo')
>>> child.depth
1

namedtuple()-style dynamic class generation#

See the implementation of namedbidict().

API Design#

How to deeply integrate with Python’s collections and other built-in APIs?

  • Beyond implementing collections.abc.Mapping, bidicts implement additional APIs that dict and OrderedDict implement (e.g. setdefault(), popitem(), etc.).

    • When creating a new API, making it familiar, memorable, and intuitive is hugely important to a good user experience.

  • Thanks to Hashable’s implementing abc.ABCMeta.__subclasshook__(), any class that implements the required methods of the Hashable interface (namely, __hash__()) makes it a virtual subclass already, no need to explicitly extend. I.e. As long as Foo implements a __hash__() method, issubclass(Foo, Hashable) will always be True, no need to explicitly subclass via class Foo(Hashable): ...

  • How to make your own open ABC like Hashable?

    • Override __subclasshook__() to check for the interface you require.

    • Interesting consequence of the __subclasshook__() design: the “subclass” relation becomes intransitive. e.g. object is a subclass of Hashable, list is a subclass of object, but list is not a subclass of Hashable.

  • What if you needed to derive from a second metaclass? Be careful to avoid “TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases”. See the great write-up in https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/.

  • collections.abc.Mapping and collections.abc.MutableMapping don’t implement __subclasshook__(), so you must either explicitly subclass them (in which case you inherit their concrete method implementations) or use abc.ABCMeta.register() (to register as a virtual subclass without inheriting any of the implementation).

  • Notice that Python provides collections.abc.Reversible but no collections.abc.Ordered or collections.abc.OrderedMapping. See: https://bugs.python.org/issue28912

  • See the Zen of Python for how to make APIs Pythonic.

    The following Zen of Python guidelines have been particularly influential for bidict: - “Errors should never pass silently. Unless explicitly silenced. - “In the face of ambiguity, refuse the temptation to guess.” - “Readability counts.” - “There should be one – and preferably only one – obvious way to do it.”

Python’s data model#

Portability#

  • CPython vs. PyPy (and other Python implementations)

  • Python 2 vs. Python 3

    • As affects bidict, mostly dict API changes, but also functions like zip(), map(), filter(), etc.

    • __ne__() fixed in Python 3

    • Borrowing methods from other classes:

      In Python 2, must grab the .im_func / __func__ attribute off the borrowed method to avoid getting TypeError: unbound method ...() must be called with ... instance as first argument

Other interesting stuff in the standard library#

Tools#

See the Thanks page for some of the fantastic tools for software verification, performance, code quality, etc. that bidict has provided a great opportunity to learn and use.

Contributors’ Guide#

Bug reports, feature requests, patches, and other contributions are warmly welcomed. Contribution should be as easy and friendly as possible. Below are a few guidelines contributors should follow to facilitate the process.

Getting Started#

  • Create a GitHub account if you don’t have one already.

  • Search through the tracker to see if an issue or pull request has already been created for what you’re interested in. If so, feel free to add comments to it or just hit the “subscribe” button to follow progress. If not, you can join the chat room to discuss there, post in the GitHub Discussions forum, or go ahead and create a new issue:

    • Clearly describe the issue giving as much relevant context as possible.

    • If it is a bug, include reproduction steps, all known environments in which the bug is exhibited, and ideally a failing test case.

  • If you would like to contribute a patch, make sure you’ve created your own fork and have cloned it to your computer.

Making Changes#

Note

You can now use GitPod.io to get an already-configured development environment inside your browser in which you can make, test, and submit your changes to bidict.

Note

You can also work on bidict in a Visual Studio Code devcontainer environment which will install development dependencies and some helpful VS Code extensions for you.

Try Remote-Containers: Clone Repository in Container Volume... on this repository. You may need to reload your VS Code window after it finishes cloning and installing extensions, which it should prompt you to do.

In a devcontainer, you don’t need to worry about the below steps of making a virtualenv or configuring EditorConfig or pre-commit, those will be part of your development environment by default.

  • Before making changes, please (create a virtualenv and) install the extra packages required for development if you haven’t already: pip install -r requirements/dev.txt

    We use EditorConfig and pre-commit to help achieve uniform style and quality standards across a diversity of development environments.

    pre-commit gets installed when you run the command above and ensures that various code checks are run before every commit (look in .pre-commit-config.yaml to see which hooks are run). Ensure the configured hooks are installed by running pre-commit install --install-hooks.

    EditorConfig allows us to provide a single .editorconfig file to configure settings like indentation consistently across a variety of supported editors. See https://editorconfig.org/#download to install the plugin for your editor.

  • Create a topic branch off of main for your changes: git checkout -b <topic> main

  • Make commits of logical units.

  • Match the existing code style and quality standards. If you’re adding a feature, include accompanying tests and documentation demonstrating its correctness and usage.

  • Run the tests locally with tox to make sure they pass for all supported Python versions (see envlist in tox.ini for the complete list). If you do not have all the referenced Python versions available locally, you can also push the changes on your branch to GitHub to automatically trigger a new GitHub Actions build, which should run the tests for all supported Python versions.

  • Create a concise but comprehensive commit message in the following style:

    Include an example commit message in CONTRIBUTING guide #9999
    
    Without this patch the CONTRIBUTING guide would contain no examples of
    a model commit message. This is a problem because the contributor is left
    to imagine what the commit message should look like and may not get it
    right. This patch fixes the problem by providing a concrete example.
    
    The first line is an imperative statement summarizing the changes with an
    issue number from the tracker. The body describes the behavior without
    the patch, why it's a problem, and how the patch fixes the problem.
    

Submitting Changes#

  • Push your changes to a topic branch in your fork of the repository: git push --set-upstream origin <topic>

  • Submit a pull request providing any additional relevant details necessary.

  • Acknowledgment should typically be fast but please allow 1-2 weeks for a full response / code review.

  • The code review process often involves some back-and-forth to get everything right before merging. This is typical of quality software projects that accept patches.

  • All communication should be supportive and appreciative of good faith efforts to contribute, creating a welcoming and inclusive community.

Sponsoring#

Sponsor through GitHub Sponsor through Gumroad Sponsor through PayPal Sponsors on GitHub

Bidict is the product of thousands of hours of my unpaid work over the ~15 years that I’ve been the sole maintainer.

If bidict has helped you or your company accomplish your work, please sponsor my work through GitHub, and/or ask your company to do the same.

Choose a tier and GitHub handles everything else. Your GitHub sponsorship will automatically go on the same bill you already have set up with GitHub, so after the one-click signup, there’s nothing else to do.

You can also sponsor my work through Gumroad or PayPal, or through a support engagement with my LLC. See Enterprise Support for details.

Code of Conduct#

All participation in this project should respect the Code of Conduct. 1

By participating, you are expected to honor this code.

1

https://bidict.readthedocs.io/code-of-conduct.html | CODE_OF_CONDUCT.rst

Code of Conduct#

Our Pledge#

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

Our Standards#

Examples of behavior that contributes to creating a positive environment include:

  • Using welcoming and inclusive language

  • Being respectful of differing viewpoints and experiences

  • Gracefully accepting constructive criticism

  • Focusing on what is best for the community

  • Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery and unwelcome sexual attention or advances

  • Trolling, insulting/derogatory comments, and personal or political attacks

  • Public or private harassment

  • Publishing others’ private information, such as a physical or electronic address, without explicit permission

  • Other conduct which could reasonably be considered inappropriate in a professional setting

Our Responsibilities#

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Scope#

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Enforcement#

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at <jabronson@gmail.com>. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership.

Attribution#

This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4.

Thanks#

Bidict has benefited from the assistance of many people and projects.

People#

  • Gregory Ewing for the name.

  • Terry Reedy for suggesting the slice syntax (it was fun while it lasted).

  • Raymond Hettinger for suggesting namedbidict(), providing feedback on the design and implementation, and (most of all) for the amazing work on Python’s built-in collections that made bidict possible.

  • Francis Carr for the idea of storing the inverse bidict.

  • Adopt Pytest Month 2015 for choosing bidict, Tom Viner for being bidict’s Adopt Pytest helper for the month, and Brianna Laugher for coordinating.

  • Daniel Pope, Leif Walsh, David Turner, and Michael Arntzenius for suggestions, code reviews, and design discussion.

  • Leif Walsh for contributing the initial devcontainer setup.

  • Jozef Knaperek for the bugfix.

  • Igor Nehoroshev for contributing the py.typed marker.

  • Bernát Gábor for pyproject.toml support.

  • Richard Sanger, Zeyi Wang, and Amol Sahsrabudhe for reporting bugs.

Projects#

bidict#

The bidirectional mapping library for Python.

Status#

Latest release Documentation GitHub Actions CI status License PyPI Downloads Sponsors on GitHub Sponsor on GitHub

Features#

  • Depended on by Google, Venmo, CERN, Baidu, Tencent, and teams across the world since 2009

  • Familiar, Pythonic APIs that are carefully designed for safety, simplicity, flexibility, and ergonomics

  • Lightweight, with no runtime dependencies outside Python’s standard library

  • Implemented in concise, well-factored, fully type-hinted Python code that is optimized for running efficiently as well as for long-term maintenance and stability (not to mention joy :)

  • Extensively documented

  • 100% test coverage running continuously across all supported Python versions

Installation#

pip install bidict

Quick Start#

>>> from bidict import bidict
>>> element_by_symbol = bidict({'H': 'hydrogen'})
>>> element_by_symbol['H']
'hydrogen'
>>> element_by_symbol.inverse['hydrogen']
'H'

For more usage documentation, head to the Introduction 3 and proceed from there.

Enterprise Support#

Enterprise-level support for bidict can be obtained via the Tidelift subscription or by contacting me directly.

I have a US-based LLC set up for invoicing, and I have 15+ years of professional experience delivering software and support to companies successfully.

You can also sponsor my work through platforms like GitHub Sponsors. See the Sponsoring section below for details, including rationale and examples of companies supporting the open source projects they depend on.

Voluntary Community Support#

Please search through already-asked questions and answers in GitHub Discussions and the issue tracker in case your question has already been addressed.

Otherwise, please feel free to start a new discussion or create a new issue on GitHub, or ask in the bidict chatroom for voluntary community support.

Notice of Usage#

If you use bidict, and especially if your usage or your organization is significant in some way, please let me know in any of the following ways:

Changelog#

See the Changelog 2 for a history of notable changes to bidict.

Release Notifications#

Watch releases on GitHub to be notified when new versions of bidict are released.

Learning from bidict#

One of the best things about bidict is that it touches a surprising number of interesting Python corners, especially given its small size and scope.

Check out Learning from bidict 1 if you’re interested in learning more.

Contributing#

I have been bidict’s sole maintainer and active contributor since I started the project almost 15 years ago.

Your help would be most welcome! See the Contributors’ Guide 4 for more information.

Sponsoring#

Sponsor through GitHub Sponsors on GitHub

Bidict is the product of thousands of hours of my unpaid work over the ~15 years that I’ve been the sole maintainer.

If bidict has helped you or your company accomplish your work, please sponsor my work through GitHub and/or ask your company to do the same.

Choose a tier and GitHub handles everything else. Your GitHub sponsorship will automatically go on the same bill you already have set up with GitHub, so after the one-click signup, there’s nothing else to do.

See the following for rationale and examples of companies supporting the open source projects they depend on in this manner:

You can also support my work through Gumroad or PayPal, or through a support engagement with my LLC. See Enterprise Support above for details.

Finding Documentation#

If you’re viewing this on https://bidict.readthedocs.io, note that multiple versions of the documentation are available, and you can choose a different version using the popup menu at the bottom-right. Please make sure you’re viewing the version of the documentation that corresponds to the version of bidict you’d like to use.

If you’re viewing this on GitHub, PyPI, or some other place that can’t render and link this documentation properly and are seeing broken links, try these alternate links instead:

1

https://bidict.readthedocs.io/learning-from-bidict.html | docs/learning-from-bidict.rst

2

https://bidict.readthedocs.io/changelog.html | CHANGELOG.rst

3
4

https://bidict.readthedocs.io/contributors-guide.html | CONTRIBUTING.rst