python-frozendict
A back-port of Python 3.15's frozendict builtin for older Python versions.
On Python 3.15+, you can write:
SETTINGS = frozendict({"debug": True, "level": 3})
This package provides the same behaviour on every supported Python version (3.8+) via a small, focused, dependency-free implementation.
Why?
A frozendict is an immutable, hashable mapping. It is useful
wherever you would reach for dict but want a value that:
- cannot be mutated by accident (or on purpose);
- is hashable and therefore usable as a
dictkey or asetmember; - serves as a clear, self-documenting signal that the data is fixed.
from frozendict import frozendict
USER_ROLES = frozendict({
"alice": "admin",
"bob": "editor",
})
# `USER_ROLES` is a valid dict key, set member, function default, etc.
cache = {USER_ROLES: "loaded"}
# Attempting to mutate it raises `TypeError`:
USER_ROLES["carol"] = "viewer"
# -> TypeError: 'frozendict' object does not support item assignment
Installation
pip install python-frozendict
Usage
Basic construction
from frozendict import frozendict
empty = frozendict()
from_dict = frozendict({"a": 1, "b": 2})
from_pairs = frozendict([("a", 1), ("b", 2)])
from_mapping = frozendict(some_Mapping_instance)
copy_of = frozendict(from_dict) # new object, same content
The constructor accepts at most one positional argument (a dict, a
Mapping, another frozendict, or any iterable of pairs). Keyword
arguments are rejected, mirroring dict itself.
Reading
frozendict supports the full read-only mapping interface:
fd = frozendict({"a": 1, "b": 2, "c": 3})
fd["a"] # 1
len(fd) # 3
"a" in fd # True
list(fd) # ["a", "b", "c"]
list(fd.keys()) # ["a", "b", "c"]
list(fd.values()) # [1, 2, 3]
list(fd.items()) # [("a", 1), ("b", 2), ("c", 3)]
fd.get("z", 0) # 0
list(reversed(fd)) # ["c", "b", "a"]
It is registered as a collections.abc.Mapping so it works with any
function that accepts a generic mapping.
Hashing and equality
frozendict is hashable, if all of its values are hashable. Two
frozendict objects with the same content compare equal and have the
same hash, independent of insertion order:
a = frozendict({"a": 1, "b": 2})
b = frozendict({"b": 2, "a": 1})
a == b # True
hash(a) == hash(b) # True
A frozendict compares equal to a dict with the same content, and
is usable as a dict key or a set element:
fd = frozendict({"a": 1})
{fd: "value"} # {frozendict({'a': 1}): "value"}
{fd, frozendict({"a": 1})} # a single-element set: {frozendict({'a': 1})}
If any value is unhashable, calling hash(fd) raises TypeError —
this mirrors the behaviour of tuple.
Merging with |
The | operator returns a new frozendict; the operands are not
mutated:
a = frozendict({"a": 1, "b": 2})
b = frozendict({"b": 99, "c": 3})
a | b # frozendict({'a': 1, 'b': 99, 'c': 3})
{"x": 0} | a # frozendict({'x': 0, 'a': 1, 'b': 2})
On key conflicts, the right-hand side wins — same as the built-in
dict merge.
Immutability
Any attempt to mutate a frozendict raises TypeError:
fd = frozendict({"a": 1})
fd["b"] = 2 # TypeError
del fd["a"] # TypeError
fd.clear() # TypeError
fd.update({"b": 2}) # TypeError
fd.setdefault("b", 2) # TypeError
fd.pop("a") # TypeError
fd.popitem() # TypeError
Copy and pickle
copy.copy(fd), copy.deepcopy(fd), and fd.copy() all return the
same object (there is no need to copy an immutable value).
Pickling and unpickling preserves content but not identity:
import pickle
fd = frozendict({"a": 1, "b": 2})
restored = pickle.loads(pickle.dumps(fd))
restored == fd # True
restored is fd # False
Subclassing
Subclassing is not supported. Both class Sub(frozendict): pass
and type("Sub", (frozendict,), {}) raise TypeError.
API
frozendict(*[mapping_or_pairs]*)
Construct a new frozendict. At most one positional argument is
accepted:
| Argument | Behaviour |
|---|---|
| omitted | empty frozendict |
dict |
shallow copy of the dict |
frozendict |
shallow copy |
collections.abc.Mapping |
shallow copy via dict(mapping) |
| iterable of (key, value) | equivalent to dict(iterable) |
Keyword arguments are rejected. Passing more than one positional
argument raises TypeError.
Methods and operators
| Member | Description |
|---|---|
fd[key] |
raise KeyError if missing |
key in fd |
membership test |
len(fd) |
number of items |
iter(fd) |
iterate over keys (insertion order) |
reversed(fd) |
iterate over keys in reverse |
fd.keys() / fd.values() / fd.items() |
mapping views (read-only) |
fd.get(key, default=None) |
safe lookup |
fd.copy() |
returns fd (immutable) |
fd == other |
content-based equality |
hash(fd) |
content-based hash (cached) |
fd | other / other | fd |
merge, returns a new frozendict |
repr(fd) / str(fd) |
"frozendict({...})" |
bool(fd) |
False for empty, True otherwise |
pickle.dumps/loads(fd) |
content preserved |
copy.copy(fd) / copy.deepcopy(fd) |
both return fd |
dict(fd) |
convert to dict |
Forbidden operations
These all raise TypeError:
| Attempted | Error message |
|---|---|
fd[key] = value |
'frozendict' object does not support item assignment |
del fd[key] |
'frozendict' object does not support item deletion |
fd.clear() |
'frozendict' object does not support item assignment |
fd.pop(...) |
'frozendict' object does not support item deletion |
fd.popitem() |
'frozendict' object does not support item deletion |
fd.setdefault(...) |
'frozendict' object does not support item assignment |
fd.update(...) |
'frozendict' object does not support item assignment |
class Sub(fd): |
subclassing frozendict is not supported |
fd.foo = "bar" |
'frozendict' object has no attribute 'foo' |
Python 3.15+ native syntax
When you are running on Python 3.15+, frozendict is a true builtin.
The package's own API still works identically, so you can use either:
# Native (Python 3.15+ only):
SETTINGS = frozendict({"debug": True})
# Cross-version equivalent via this package:
from frozendict import frozendict
SETTINGS = frozendict({"debug": True})
Compatibility
- Python 3.8 through 3.15+ (uses
collections.abc.Mapping; on 3.9+ uses modern union / merge syntax in the docs only). - No third-party dependencies — uses only the standard library.
Running the tests
python -m unittest test_frozendict.py -v
Retirement
This project will reach its end-of-life around October 1, 2031 —
the official EOL date of Python 3.15 — and the exact timeline could be
slightly delayed. We plan to ship the final stable release in
November 2031. After this release, all support will cease and the
repository will be officially archived, as this library is developed
solely to bring frozendict compatibility to Python 3.15 and older
versions.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_frozendict-0.1.0.tar.gz.
File metadata
- Download URL: python_frozendict-0.1.0.tar.gz
- Upload date:
- Size: 14.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b78395973abb05431b9333a651d0a02a71e500ec7546070e188f353c9c6f2103
|
|
| MD5 |
51c742940ec2a15c077209eadeef038f
|
|
| BLAKE2b-256 |
e0c46357cef668a77399fda43def63b22268a146b1be31ff06246c3d72170ecd
|
File details
Details for the file python_frozendict-0.1.0-py3-none-any.whl.
File metadata
- Download URL: python_frozendict-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08aa62fae8fc2606014f775a4af3d1094ddae7aefe747c169e8c40e259acc9eb
|
|
| MD5 |
cb083722fe3a0eeaeda92bea4ebcb2a1
|
|
| BLAKE2b-256 |
bb38ece18ac2625730152e060d0e8080991a7cd378fa13d69902c54076d58317
|