mrkeyoor.com_
Sat 08 Aug 21:01 UTC
PyPIUtilsupdated 08 Aug 2026

immutabledict

immutabledict is a pure-Python Mapping that copies dictionary input and blocks item assignment, deletion, and in-place union. It supports normal read operations, hashing when every key and value is hashable, typing through a py.typed marker, ordered variants, and copy-on-write helpers named set, update, delete, and discard. It exists as a small MIT-licensed fork of the original frozendict project and is useful when a mapping itself must be a stable value or dictionary key.

Verdict

A sound tiny wrapper for small, shallow mappings that are built once and read often. Do not mistake it for recursive freezing or an efficient persistent map; repeated updates and mutable nested values are the line where another design wins.

API stability5/5The package implements the standard Mapping interface and a small set of explicit copy-on-write helpers. Its README records the notable compatibility decision that copy has matched dict's no-keyword signature since version 3.0, while version 4.3.1 retains set, update, delete, discard, fromkeys, ordered mapping, union operators, hashing, equality, and pickle support.
Docs3/5The README clearly explains the fork, license motivation, installation, basic lookup, typing, union operators, and added mutation-returning methods, and the linked documentation resolves successfully. It does not prominently explain shallow immutability, the all-values-must-be-hashable requirement, O(n) copies for every update, cached-hash hazards with mutable custom values, or result types for reversed union order.
Maintenance5/5PyPI shows version 4.3.1 released in February 2026 and GitHub reports a push in August 2026. The repository is not archived, supports Python 3.8 through 3.14 in its package classifiers, ships a py.typed marker, and has only one open item in GitHub's combined issues-and-pull-requests count. That is healthy upkeep for a compact stable container.
Ecosystem3/5The package recorded 5,518,564 downloads in the latest measured week, but the source repository has only fifty-four stars and the concept needs few integrations. It interoperates cleanly through collections.abc.Mapping, ordinary dictionary equality, PEP 584 unions, pickle, and static type checkers. Teams needing deep or persistent collections must move to a different ecosystem.

Use it if

  • You need a hashable mapping for cache keys, sets, graph nodes, or memoization and all contained values are hashable
  • A read-only Mapping API plus explicit copy-on-write set, update, delete, and discard operations fits your code
  • You want a pure-Python MIT-licensed mapping with no runtime dependencies and published typing information
  • You need an ImmutableOrderedDict for code that explicitly depends on OrderedDict behavior
Skip it if

Setup reality

`pip install immutabledict` installs one pure-Python package with no runtime dependencies. Version 4.3.1 supports Python 3.8 through the current Python 3 line but explicitly excludes Python 4. Construction makes a shallow copy of the supplied mapping, so later top-level edits to the original dict do not appear. That does not freeze nested values: if both structures reference the same list or custom object, mutating it still changes what readers observe. Hashing is lazy and cached. Every contained key and value must therefore be hashable, and mutating a badly chosen nested custom object after the first hash can violate dictionary-key assumptions even though the wrapper itself never assigns anything. Copy-on-write helpers are convenient but not persistent data structures: source shows set, delete, update, and `|` each build a normal dict and then wrap it, making every change O(n) in map size. `copy()` returns a new object and, since 3.0, deliberately accepts no dict-style keyword changes. `left | right` takes its result type from the left side: immutabledict on the left returns immutabledict, while a normal dict on the left returns dict. `|=` always raises for an immutabledict. Equality follows Mapping behavior and does not enforce the same concrete type. Pickling is supported and deliberately omits the cached hash because Python hash values can change between interpreter runs.

Patterns

Create and read an immutable mappingcreate-readonly-mapping

from immutabledict import immutabledict

settings = immutabledict({'region': 'eu-west-1', 'retries': 3})
print(settings['region'])
print(settings.get('timeout', 30))

Construction copies the outer mapping, so later top-level changes to the source dict do not affect settings.

Annotate key and value typestype-the-mapping

from immutabledict import immutabledict

ports: immutabledict[str, int] = immutabledict({'http': 80, 'https': 443})
for name, port in ports.items():
    print(name, port)

The package ships a py.typed marker. Value type is covariant, which helps when exposing read-only mappings through broader interfaces.

Build from a sequence of keyscreate-from-keys

from immutabledict import immutabledict

flags = immutabledict.fromkeys(['read', 'write'], False)
assert flags == {'read': False, 'write': False}

As with dict.fromkeys, every key receives the same value object. Avoid one shared mutable default.

Return a copy with one value setset-copy-on-write

from immutabledict import immutabledict

base = immutabledict({'retries': 2})
updated = base.set('retries', 3)
assert base['retries'] == 2
assert updated['retries'] == 3

set copies the full underlying dictionary. It is not structural sharing and gets costly for large maps with frequent changes.

Return a copy with several updatesupdate-copy-on-write

from immutabledict import immutabledict

base = immutabledict({'host': 'localhost', 'port': 8000})
production = base.update({'host': 'api.example.com', 'port': 443})

update expects a dictionary and returns a new immutabledict. The original remains unchanged.

Remove a known keydelete-required-key

from immutabledict import immutabledict

record = immutabledict({'id': 7, 'debug': True})
clean = record.delete('debug')

delete raises KeyError when the key is absent, matching del-style expectations while still returning a new object.

Remove a key only if presentdiscard-optional-key

from immutabledict import immutabledict

record = immutabledict({'id': 7})
unchanged = record.discard('debug')
assert unchanged is record

discard returns the same object when the key is missing, avoiding an unnecessary full copy.

Merge with the union operatormerge-mappings

from immutabledict import immutabledict

base = immutabledict({'color': 'blue', 'size': 'm'})
merged = base | {'size': 'l', 'stock': 4}
assert isinstance(merged, immutabledict)
assert merged['size'] == 'l'

Right-hand values win. In-place union with |= raises TypeError because it would imply mutation.

Keep result type explicit in reverse unionsunderstand-reverse-union

from immutabledict import immutabledict

fixed = immutabledict({'timeout': 10})
result = {'retries': 2} | fixed
assert isinstance(result, dict)

A normal dict on the left produces a mutable dict. Put immutabledict on the left or wrap the result when the output must stay immutable.

Use a mapping as a cache keyuse-as-dictionary-key

from immutabledict import immutabledict

query = immutabledict({'page': 2, 'limit': 50})
cache = {query: ['row-1', 'row-2']}
print(cache[query])

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

Freeze nested values explicitlyavoid-shallow-mutation

from immutabledict import immutabledict

permissions = immutabledict({
    'admin': frozenset({'read', 'write'}),
    'viewer': frozenset({'read'}),
})
assert hash(permissions)

immutabledict freezes only its own key assignments. Use immutable nested values when stable hashing or deep immutability matters.

Use the ordered variantpreserve-explicit-order

from immutabledict import ImmutableOrderedDict

steps = ImmutableOrderedDict([('build', 1), ('test', 2), ('ship', 3)])
assert list(steps) == ['build', 'test', 'ship']

Modern dict already preserves insertion order. Choose this variant only when OrderedDict identity or behavior is part of the contract.

Alternatives

PackageRegistryPick it when
frozendictPyPIYou want the better-known frozen mapping package and its API or licensing fits your project
pyrsistentPyPIYou need persistent maps, vectors, sets, and nested transformations with structural sharing
immutablesPyPIYou update large immutable mappings often and want a high-performance HAMT implementation