mrkeyoor.com_
Wed 23 Sept 00:35 UTC
PyPIUtilsupdated 22 Sept 2026

immutabledict review

immutabledict 4.3.1 wraps a Python `dict` behind the complete read-only `Mapping` interface. Indexing, iteration, length, equality, hashing, union operators, and pickling work, while assignment and in-place union do not. Methods named `set`, `delete`, `discard`, and `update` copy the top-level mapping and return another immutabledict. The latest patch adjusts constructor overloads for pytype and pyrefly; version 4.3.0 also fixed `ImmutableOrderedDict`, whose backing class had silently been plain `dict`. Values are not frozen, so a list or nested dictionary inside the wrapper remains mutable.

Verdict

immutabledict 4.3.1 installed in 0.2 seconds as 1 dependency-free MB and imported in 0.09 seconds in our sandbox, so it is a low-cost choice for small, typed, top-level immutable mappings. Choose a persistent map for heavy revision workloads, and never treat mutable nested values as frozen.

We installed it

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

Answers from our run

Does immutabledict install cleanly?

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

What does immutabledict need to run?

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

immutabledict or frozendict: which should you use?

frozendict: Use it when its mature frozen-mapping implementation fits and the LGPL-3.0 license is acceptable to your project. immutabledict 4.3.1 installed in 0.2 seconds as 1 dependency-free MB and imported in 0.09 seconds in our sandbox, so it is a low-cost choice for small, typed, top-level immutable mappings.

When should you not use immutabledict?

Nested values must also be immutable. The constructor copies the outer mapping into a regular dictionary and retains references to lists, sets, objects, and nested dictionaries.

API stability4/5The class implements Python's Mapping contract and keeps a deliberately short surface: construction, lookup, iteration, hashing, union, copy, set, delete, update, and discard. Version 3.0 changed `copy()` to reject keyword updates, and version 4.0 replaced initialization internals while adding the copy-on-write helpers. The 4.3 line changes overloads and fixes the ordered subclass without changing normal immutabledict runtime calls.
Docs3/5The project site documents installation, construction, the mapping methods, union behavior, pickling, and the four methods that return modified copies. The README clearly states that the package is a fork and explains the licensing reason for it. Important limits require reading the implementation or API pages: values are shallowly retained, hashing needs hashable values, each update copies the backing dictionary, and reverse union returns a plain dict.
Maintenance4/5GitHub shows an unarchived repository pushed on August 10, 2026, with 2 open issues and pull requests. Release 4.3.1 shipped February 15, 2026 to fix pytype and pyrefly typing problems, five days after 4.3.0 added constructor overloads and corrected the ordered implementation. PyPI classifiers cover Python 3.8 through 3.14 and both CPython and PyPy, which is a concrete current-version support signal.
Ecosystem3/5immutabledict uses the standard collections.abc Mapping protocol, so consumers normally need no package-specific adapter. Its pure-Python wheel, `py.typed` marker, no-dependency install, and Python 3.8 to 3.14 classifiers make it straightforward in typed applications. The project remains a narrow utility with 53 GitHub stars and no plugin family; built-in MappingProxyType, frozendict, and persistent-collection packages cover adjacent needs.

Use it if

  • A public API should return a Mapping that callers cannot change through item assignment.
  • Configuration keys and values are hashable and the whole mapping needs to work as a set member or dictionary key.
  • A typed Python 3.8 through 3.14 project wants a pure-Python mapping with no runtime dependencies.
  • Code benefits from copy-on-write helpers for small mappings and does not need structural sharing across large versions.
Skip it if

Setup reality

We installed immutabledict 4.3.1 in a fresh Python 3.12 Bookworm sandbox. The install completed in 0.2 seconds and left 1 package using 1 MB on disk. It has 0 direct dependencies, is pure Python, requires Python 3.8 or newer and lower than 4.0, and ships a py.typed marker. pip-audit found 0 known vulnerabilities.

No credentials, service, native compiler, or config file is involved. import immutabledict worked in 0.09 seconds on our box. PyPI publishes a universal Python 3 wheel. Its package metadata leaves the license field empty even though the repository identifies an MIT license, so automated license policy should inspect the repository license rather than relying on that one metadata field.

Immutability stops at the first level. immutabledict({"items": []}) still exposes the same mutable list, and changing that list after a hash has been cached can break assumptions made by sets or dictionary keys. Use hashable, stable values whenever an instance will be hashed. Construction copies the input mapping, so later top-level changes to the original dictionary do not appear in the wrapper.

Every set, delete, or update call allocates and fills another top-level dictionary. delete raises KeyError for a missing key; discard returns the same object when the key is absent. The left | right result follows the left operand: an immutabledict on the left returns an immutabledict, while a plain dict on the left returns a dict. Version 4.3.1 only changes typing behavior, so runtime users do not need a migration.

Patterns

Freeze the top level of a dictionary create-mapping

from immutabledict import immutabledict

settings = immutabledict({"region": "eu", "retries": 2})
print(settings["region"])

The constructor copies the top-level input dictionary. Objects stored as values keep their original identity and mutability.

Confirm callers cannot assign a key reject-assignment

settings = immutabledict({"region": "eu"})

try:
    settings["region"] = "us"
except TypeError:
    pass

The class implements Mapping rather than MutableMapping, so item assignment has no supported method.

Return a copy with one value changed set-value

base = immutabledict({"region": "eu", "retries": 2})
changed = base.set("retries", 3)

assert base["retries"] == 2
assert changed["retries"] == 3

`set` copies the complete backing dictionary. Repeated edits to a large mapping do not share structure.

Apply several changes in one copy update-values

base = immutabledict({"region": "eu", "retries": 2})
changed = base.update({"region": "us", "timeout": 5})

The 4.3.1 method accepts a dictionary and returns a new immutabledict; it does not mutate `base`.

Remove a key and fail if it is absent delete-required-key

without_timeout = settings.delete("timeout")

try:
    settings.delete("missing")
except KeyError:
    pass

`delete` mirrors strict dictionary deletion and raises `KeyError` for an unknown key.

Remove a key only when present discard-optional-key

clean = settings.discard("debug")

assert clean is settings  # when debug was absent

Version 4.2 added `discard`. It returns the same instance when the key is missing, avoiding an unnecessary copy.

Merge mappings with right-hand values winning merge-right-biased

defaults = immutabledict({"region": "eu", "retries": 2})
settings = defaults | {"retries": 4, "debug": True}

assert isinstance(settings, immutabledict)

With immutabledict on the left, `|` returns an immutabledict and later values replace duplicate keys.

Know when union returns a regular dictionary reverse-union

frozen = immutabledict({"retries": 2})
result = {"region": "eu"} | frozen

assert type(result) is dict

A plain dict on the left calls the reverse union path, whose return type is `dict`, not immutabledict.

Use a mapping with hashable contents as a key use-as-key

query = immutabledict({"region": "eu", "page": 1})
cache = {query: "result"}

assert cache[query] == "result"

Every key and value must be hashable. A list or nested dict value makes `hash(query)` raise `TypeError`.

Freeze nested data explicitly avoid-shallow-mutation

roles = ("reader", "editor")
user = immutabledict({"id": 7, "roles": roles})

Use tuples, frozensets, or recursively frozen mappings for nested data. immutabledict freezes only its own outer mapping.

Build a typed mapping from key-value pairs construct-from-pairs

ports = immutabledict([("http", 80), ("https", 443)])
assert list(ports) == ["http", "https"]

Version 4.3 adds typed constructor overloads for iterables and mappings; 4.3.1 corrects their behavior in pytype and pyrefly.

Round-trip through pickle pickle-mapping

import pickle

frozen = immutabledict({"region": "eu"})
restored = pickle.loads(pickle.dumps(frozen))
assert restored == frozen

The pickle payload stores the mapping data and omits the cached hash, since Python hash values can differ between interpreter runs.

Alternatives

PackageRegistryPick it when
frozendictPyPIUse it when its mature frozen-mapping implementation fits and the LGPL-3.0 license is acceptable to your project.
pyrsistentPyPIUse its PMap when frequent updates to larger mappings need structural sharing instead of a full top-level copy.
frozenmapPyPIUse it when a C or Cython-backed frozen mapping is worth adding compiled-extension packaging concerns.

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.