mrkeyoor.com_
Tue 22 Sept 18:49 UTC
PyPIUtilsupdated 22 Sept 2026

bidict review

bidict is a Python mapping for one-to-one relationships. It indexes every pair in both directions, so `codes['GB']` can return a country name and `codes.inverse['United Kingdom']` can return the code without scanning the mapping. The inverse is a live view of the same pairs. The package also supplies `frozenbidict` for immutable, hashable mappings and `OrderedBidict` when forward and reverse iteration order must agree. Version 0.24.0 raises the Python floor to 3.11, removes `bidict.__version__` and other module metadata, repairs rollback during failed bulk writes, fixes several ordering cases, and deliberately stops reading pickles created by 0.23.1 or older.

Verdict

Install bidict when the data is truly one-to-one and both lookup directions are part of normal application flow. Avoid it for one-to-many relationships, and treat the 0.24.0 Python floor and pickle break as real upgrade work.

We installed it

Lab card: what happened when we installed bidictScreenshot of bidict documentation
Install✓ · 0.2s1 package on disk · 1 MB
Importimport bidict in 0.09s · pure Python · py.typed · requires Python >=3.8
Known vulns0(pip-audit)

Answers from our run

Does bidict install cleanly?

Yes. In a fresh container with an empty cache, pip install bidict finished in 0.2s, leaving 1 package and 1 MB on disk. pip-audit reported no known vulnerabilities.

What does bidict need to run?

Python >=3.8, and nothing compiled: it is pure Python. In our run import bidict succeeded in 0.09s, and the package ships py.typed for type checkers.

bidict or multidict: which should you use?

multidict: Choose it for one key with several values, especially repeated HTTP-style fields. Install bidict when the data is truly one-to-one and both lookup directions are part of normal application flow.

When should you not use bidict?

Several keys can point to the same value. bidict rejects that shape because the reverse lookup would be ambiguous; use a multimap or a dictionary of sets.

API stability4/5The central API remains small and follows Python mapping conventions: item access, mutation, `inverse`, explicit duplicate policies, and mutable, frozen, and ordered variants. Version 0.24.0 keeps those operations but removes `bidict.__version__` and related module metadata. It also breaks old pickles by dropping a private compatibility path, so ordinary source code is steady while serialized-object and introspection contracts require attention.
Docs5/5The official documentation explains every duplicate combination, the difference between `put` and `forceput`, bulk rollback, inverse mutability, hashability, ordered behavior, custom backing mappings, and type annotations. The 0.24.0 changelog names the exact failure modes it fixed, including mismatched key/value iteration and partial bulk writes. Examples show resulting pairs and exceptions, which makes the unusual semantics testable instead of leaving them implicit.
Maintenance4/5Release 0.24.0 was published on August 25, 2026, and the repository was pushed the same day. GitHub reports two open issues and pull requests, 1,584 stars, an unarchived repository, and a main default branch. The release fixes invariant, rollback, ordering, view, pickle, and performance problems rather than changing the package's purpose. Bus factor remains the concern because the README identifies one maintainer and active contributor over more than 15 years.
Ecosystem4/5PyPIStats counted 7,239,945 downloads in the latest week. bidict implements familiar mapping interfaces, ships typing metadata in the measured 0.23.1 package, and has no direct dependencies there, so it fits into typed Python code without an adapter layer. Its ecosystem is intentionally narrow: there are no databases, serializers, or plugin families bundled around it. The value comes from one well-defined container rather than a broad integration catalog.

Use it if

  • Each value is unique by domain rule, such as an external account ID paired with one internal account ID.
  • Code reads the relationship in both directions often enough that rebuilding a reverse dictionary would be wasteful or error-prone.
  • A duplicate value should raise at the write site instead of silently replacing an unrelated pair.
  • You need the same bidirectional API in mutable, immutable, or order-aware forms.
Skip it if

Setup reality

We installed bidict 0.23.1 in a clean Python 3.12 Bookworm container. The install finished in 0.2 seconds and left one package using 1 MB. It had no direct dependencies, pip-audit found no known vulnerabilities, and import bidict completed in 0.09 seconds. That release is pure Python, includes py.typed, uses MPL 2.0, and accepts Python 3.8 or newer. PyPI now serves 0.24.0, which requires Python 3.11 or newer, so older runtimes cannot take the current release.

The design constraint appears at the first write. Keys and values must be hashable, and values must be unique. Reusing a value raises ValueDuplicationError. Replacing an existing key with a new value follows dictionary behavior, while put() lets you choose an OnDup policy. forceput() and forceupdate() may discard conflicting pairs to restore the one-to-one invariant, so keep those calls visible in code review. Version 0.24.0 now rolls a bulk update back even when failure comes from unpacking, hashing, or a custom backing mapping rather than a duplication error.

inverse does not allocate a detached dictionary. It shares storage and mutability with the original object, which means a helper that receives mapping.inverse can change mapping. Use frozenbidict or pass a copy when that write access is unwanted. Plain bidict iteration now keeps keys() and values() paired correctly in 0.24.0, but use OrderedBidict when mutations must preserve the same order in both directions.

Two upgrade details deserve a deployment check. Read the installed version through importlib.metadata.version('bidict') because 0.24.0 removed bidict.__version__. Rebuild or migrate stored data instead of unpickling 0.23.1-era bidict objects under 0.24.0; the new release intentionally rejects those old pickles.

Patterns

Read a pair from either side lookup-both-directions

from bidict import bidict

country_by_code = bidict({'GB': 'United Kingdom', 'JP': 'Japan'})
print(country_by_code['GB'])
print(country_by_code.inverse['Japan'])

The inverse lookup is backed by the same mapping and does not scan all pairs.

Add a pair through the inverse view write-through-inverse

country_by_code.inverse['Canada'] = 'CA'

assert country_by_code['CA'] == 'Canada'

A mutable inverse writes into the original mapping. Treat it as shared write access, not a copy.

Reject an ambiguous reverse lookup catch-duplicate-value

from bidict import ValueDuplicationError, bidict

emails = bidict({1: 'ada@example.com'})
try:
    emails[2] = 'ada@example.com'
except ValueDuplicationError:
    print('email already assigned')

The existing pair remains because one value cannot identify two keys.

Reject duplicate keys and values enforce-strict-write

from bidict import ON_DUP_RAISE, bidict

statuses = bidict({'ok': 200})
statuses.put('created', 201, on_dup=ON_DUP_RAISE)

Normal item assignment may replace an existing key. ON_DUP_RAISE makes both sides strict.

Move a value to a new key replace-conflict-deliberately

from bidict import bidict

owner_by_id = bidict({'old-id': 'alice'})
owner_by_id.forceput('new-id', 'alice')

assert 'old-id' not in owner_by_id

forceput removes conflicting associations. Use it only when replacement is the business rule.

Apply a strict batch bulk-write-atomically

from bidict import ON_DUP_RAISE, bidict

codes = bidict({'ok': 200})
codes.putall([('created', 201), ('accepted', 202)], on_dup=ON_DUP_RAISE)

Version 0.24.0 rolls back the whole call when unpacking, hashing, backing-map writes, or duplicate handling raises.

Replace keys but protect values choose-duplicate-policy

from bidict import DROP_OLD, RAISE, OnDup, bidict

labels = bidict({1: 'one'})
policy = OnDup(key=DROP_OLD, val=RAISE)
labels.put(1, 'uno', on_dup=policy)

Separate key and value actions make the intended loss explicit. Test a pair that conflicts on both sides.

Use a bidict as a cache key freeze-mapping

from bidict import frozenbidict

translation = frozenbidict({'yes': 'si', 'no': 'no'})
cache = {translation: 'spanish'}
assert cache[translation] == 'spanish'

frozenbidict is immutable and hashable when all contained keys and values are hashable.

Keep both iteration orders aligned preserve-reverse-order

from bidict import OrderedBidict

steps = OrderedBidict([(1, 'draft'), (2, 'review')])
steps[2] = 'approved'
assert list(steps.values()) == list(steps.inverse.keys())

Use OrderedBidict when mutation order is part of the contract on both views.

Compare mapping order explicitly compare-order

from bidict import OrderedBidict

a = OrderedBidict([(1, 'a'), (2, 'b')])
b = OrderedBidict([(2, 'b'), (1, 'a')])
assert a == b
assert not a.equals_order_sensitive(b)

Normal mapping equality ignores order. equals_order_sensitive checks pair sequence too.

Swap pairs without a live bidict invert-pair-stream

from bidict import inverted

pairs = [('GB', 'United Kingdom'), ('JP', 'Japan')]
code_by_country = dict(inverted(pairs))

The resulting dictionary is independent and will not stay synchronized with the original pairs.

Read package metadata on 0.24 read-installed-version

from importlib.metadata import version

print(version('bidict'))

Version 0.24.0 removed bidict.__version__; use importlib.metadata for installed package metadata.

Alternatives

PackageRegistryPick it when
multidictPyPIChoose it for one key with several values, especially repeated HTTP-style fields.
python-benedictPyPIChoose it when nested key paths, conversions, and file-format helpers matter more than a live inverse.
two-way-dictPyPIKeep it in an existing project already written around its smaller two-way mapping API.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.