Skip to main content

🧊 A memory-efficient, fully immutable dictionary for Python, powered by Rust.

Project description

frozndict - Rust-Powered Immutable Dictionary License PyPI version Repo Size CircleCI Build Status

frozndict is a production-grade, fully immutable Python dictionary powered by Rust and PyO3. It is a drop-in companion to frozenset for dictionaries: hashable, thread-safe, insertion-ordered, and engineered for performance.

🚀 Performance Highlights

frozndict operates natively at the mathematical speed limits of the hardware, heavily outperforming its C counterparts and standard dictionary data structures on several fronts.

The following is a 1000-element dictionary micro-benchmark comparison in seconds (Smaller is better).

Operation (1000 int)

Python dict

immutables.Map

frozendict (C)

frozndict 🧊

Construction

4.92e-06 🏆

1.61e-04

6.19e-06

5.79e-05

Clone O(1)

4.88e-06

9.87e-08 🏆

4.38e-07

1.29e-07

Equality O(1)

1.66e-05

2.52e-08 🏆

1.66e-05

3.24e-08

Iteration

1.15e-05

1.69e-05

1.15e-05

6.08e-06 🏆

Copy O(1)

4.88e-06

2.17e-04

6.58e-08

6.42e-08 🏆

  1. 🏆 Fastest Iteration in Class via Lazy Caching Keys, values, and items are built lazily on first access via a Rust OnceCell. This means construction is ultra-fast, and every subsequent keys() / values() / items() / iteration call returns an O(1) view object wrapping a shared Arc, no per-call allocation at all.

  2. Deterministic O(1) Pre-Computed Hashing The dict-level hash is XOR-combined over all (k, v) pairs exactly once at construction via inline multiplicative mixing, skipping the slow allocation of intermediate PyTuple instances entirely.

  3. O(1) ``copy()`` / ``deepcopy()`` & Clones A shared Arc<FrozenDictInner> is re-used across the original, all copies, and mapping constructors (frozendict(existing_fd)). No data is ever duplicated.

  4. Instantaneous O(1) ``__eq__`` short-circuit Self-equality (o == o) returns true instantly via Arc::ptr_eq. Equality between distinct FrozenDict instances first compares the pre-computed hashes, detecting mismatches in O(1) before any scanning.

  5. 🏆 Smallest Memory Footprint in Class frozndict has the smallest memory footprint compared to other Python/C alternatives (occupying just 24 bytes of overhead for the main wrapper class). Entries sit in a single flat Box<[(isize, K, V)]> allocation plus a compact Box<[(isize, u32)]> lookup index, eliminating load-factor slack completely.

  6. Infallible Rust-Level Immutability Mutation is blocked safely at the Rust binary level, completely bypassing Python descriptor tricks.

🛠️ Requirements

  • Python 3.12+

  • Rust 1.89+ (only when building from source)

  • Maturin 1.14+ (only when building from source)

🚨 Installation

With pip:

python3 -m pip install frozndict

Build from source for development:

git clone https://github.com/wiseaidev/frozndict.git
cd frozndict
python3 -m venv .venv
source .venv/bin/activate
pip install maturin
maturin develop --features python

🚸 Usage

>>> from frozndict import frozendict

# Empty immutable dictionary.
>>> frozendict({})
frozendict({})

# Insertion order is preserved.
>>> frozendict(b=2, a=1, c=3)
frozendict({'b': 2, 'a': 1, 'c': 3})

>>> list(frozendict(b=2, a=1).keys())
['b', 'a']

# Set operations on keys.
>>> frozendict(a=1, b=2).keys() & {"a", "x"}
frozenset({'a'})

# Set operations on items.
>>> frozendict(a=1, b=2).items() - {("a", 1)}
frozenset({('b', 2)})

# Pickle round-trip.
>>> import pickle
>>> d = frozendict(a=1, b=2)
>>> pickle.loads(pickle.dumps(d)) == d
True

# copy / deepcopy (O(1)).
>>> import copy
>>> copy.copy(d) == d
True

# Mapping ABC.
>>> import collections.abc
>>> isinstance(d, collections.abc.Mapping)
True

# Subclassing.
>>> class MyFrozen(frozendict):
...     def summary(self): return f"{len(self)} keys"
>>> MyFrozen(a=1, b=2).summary()
'2 keys'

# Generic subscript.
>>> frozendict[str, int]
frozndict.FrozenDict[str, int]

# fromkeys constructor.
>>> frozendict.fromkeys(["x", "y"], "5")
frozendict({'x': '5', 'y': '5'})

# Fully hashable and usable as dict keys or set members.
>>> set([frozendict(a=1, b=2), frozendict(b=2, a=1)])
{frozendict({'a': 1, 'b': 2})}

# Mutation raises TypeError at the Rust binary level.
>>> d["x"] = 1
TypeError: 'frozendict' object does not support mutation

# Memory footprint comparison: frozndict (Rust) wrapper matches pointer size (24 bytes)
>>> import frozendict
>>> frozendict.c_ext
False
>>> frozendict.__version__
'2.4.7'
>>> from frozendict import frozendict
>>> from frozndict import frozendict as frozndict
>>> d = {'x': 3, 'y': 4, 'z': {'a': 0, 'b': [3,1,{4,1},[5,9]]}}
>>> classes = (dict, frozndict, frozendict)
>>> objects = [k(d, c=1) for k in classes]
>>> import sys
# sys.getsizeof shows: dict (184B) vs frozndict Rust (24B) vs frozendict C (192B)
>>> tuple(map(sys.getsizeof, objects))
(184, 24, 192)

📊 Running Benchmarks

$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install maturin frozendict immutables
$ maturin develop --release --features python
$ python3 benchmarks/benchmark.py

The benchmark compares frozndict (Rust), frozendict (C), dict, and immutables.Map across construction, lookup, iteration, copy, pickle, hash, and set/delete operations.

🎉 Credits

Built with: python · PyO3 · maturin · pytest · black · isort · flake8 · precommit

👋 Contribute

Please refer to the Guideline for contribution instructions.

📝 License

Released under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

frozndict-2.1.0-cp312-cp312-manylinux_2_34_x86_64.whl (246.9 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.34+ x86-64

File details

Details for the file frozndict-2.1.0-cp312-cp312-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for frozndict-2.1.0-cp312-cp312-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 3821e0558365e9a28f7b6e3f61592c1807330891349f7bbf642dda7905a7306c
MD5 baa4f20490476f76be19bfcc17df3925
BLAKE2b-256 2ff673728cc0f51ad0adb0c512a3ecfedd0df8a30419c223380499ae9b343b84

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page