pyrsistent

Persistent, immutable, functional data structures for Python — drop-in replacements for list, dict, and set that never mutate.

Library
PyPI
v0.20.0
2,200stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
43/100Fair
Development Activity4
Maintenance0
Community68
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
76/100Good
Architecture78
Code Quality82
Innovation68
Learning Curve75

Pyrsistent provides a set of persistent (immutable) collection types for Python — PVector, PMap, PSet, PBag, PList, PDeque, and record/class-based structures — modeled after the persistent data structures found in Clojure’s standard library. Every method that would normally mutate a collection instead returns a new, updated copy while leaving the original untouched, using structural sharing (path copying through a bitmapped vector trie for PVector, a hash-array-mapped trie for PMap) so updates stay cheap even on large structures.

On top of the core collections, the library adds PRecord and PClass — fixed-field, dict-like or object-like structures with optional type checking, field-level and record-level invariants, factories, and serialization to plain dicts — plus checked collection variants (CheckedPVector, CheckedPMap, CheckedPSet) that enforce element types at runtime. A freeze/thaw pair converts between Python’s built-in mutable collections and their persistent equivalents, and a small transformation API lets you update deeply nested structures in one expression instead of manually rebuilding each level.

The project ships an optional C extension (pvectorc) that reimplements PVector for a significant speed boost, falling back transparently to a pure-Python implementation when the extension can’t be built (e.g. PyPy, unsupported platforms, or PYRSISTENT_SKIP_EXTENSION set). This makes it practical to use persistent structures in performance-sensitive code without giving up portability.

What You Get

  • PVector, PMap, and PSet as immutable, Sequence/Mapping/Set-protocol-compatible drop-ins for list, dict, and set, each hashable so they can be used as dict keys
  • PRecord and PClass for fixed-field structures with optional per-field and per-record type checking, invariants, factories, and dict serialization
  • Checked collection variants (CheckedPVector, CheckedPMap, CheckedPSet) that enforce element/key/value types at runtime and raise InvariantException on violation
  • freeze() and thaw() helpers to convert recursively between Python’s built-in mutable collections and their pyrsistent equivalents
  • A transformation API (transform, inc, discard, rex, ny) for updating deeply nested persistent structures in a single expression
  • An optional compiled C extension (pvectorc) for PVector that improves performance while degrading gracefully to pure Python when unavailable

Common Use Cases

  • Sharing state safely across threads or coroutines without locks, since no persistent structure can be mutated out from under another reference holder
  • Modeling application or domain state (e.g. in Redux-style or event-sourced architectures) where every state transition must produce a new, independently inspectable snapshot
  • Building typed, validated configuration or record objects with PRecord/PClass instead of hand-rolled dataclasses plus manual validation
  • Using immutable collections as dictionary keys or set members, which plain Python lists and dicts cannot do
  • Converting between persistent and native Python collections at API boundaries with freeze()/thaw() when interfacing with code that expects built-in types

Under The Hood

Architecture The core is organized as one module per collection type (_pvector.py, _pmap.py, _pset.py, _pbag.py, _plist.py, _pdeque.py), all re-exported through a flat init.py. PVector’s PythonPVector class implements a bitmapped vector trie (branch factor 32) using structural sharing: an update produces new nodes only along the path from root to the changed leaf, while every other node stays shared with the original structure. A parallel C extension (pvectorcmodule.c) reimplements the same trie for performance; setup.py conditionally builds it and a custom build_ext subclass catches build failures so installation still succeeds with a pure-Python fallback. PRecord and PClass layer typed, invariant-checked, fixed-field structures on top of PMap via shared field descriptors in _field_common.py, and _transformations.py implements a generic tree-walking updater that dispatches across PMap/PVector uniformly through structural protocols rather than concrete types.

Tech Stack Pure Python 3.10+ with no runtime dependencies beyond the standard library; the optional C extension is built with setuptools’ Extension/build_ext. Test dependencies (pytest, hypothesis, typing_extensions) are declared in tox.ini, which drives a matrix across CPython 3.10-3.14 and PyPy 3.11. Type information ships as py.typed plus separate .pyi stub files for static checkers. CI runs the full tox matrix on every push/PR plus a dedicated coverage job that disables the C extension to get accurate Python-level coverage, uploading results to Coveralls. Documentation builds via Sphinx and is hosted on ReadTheDocs.

Code Quality Tests are organized one file per collection type, with dedicated suites for checked variants, transformations, and freeze/thaw conversion; hypothesis_vector_test.py adds property-based testing for PVector via the Hypothesis library, and a separate tox environment runs the README’s embedded examples as doctests. Error handling is explicit and typed rather than generic: InvariantException, CheckedKeyTypeError, CheckedValueTypeError, and PTypeError are dedicated exception classes that carry structured failure detail (missing fields, invariant errors) instead of swallowing failures. Naming is consistent throughout (underscore-prefixed private modules, p-prefixed public constructors). The main gap is that CI runs tests and coverage but has no separate lint/format enforcement job.

API Design The public API deliberately mirrors Python’s built-in collection protocols (Sequence, Mapping, Set, Hashable), so PVector/PMap/PSet slot into code written against list/dict/set with minimal friction, and short constructor aliases (v(), m(), s()) keep call sites terse. The API asks more of the developer around PRecord/PClass, whose field/invariant/factory system is more expressive but has a steeper learning curve than a plain dataclass, and around the C-extension fallback, which works transparently but is underdocumented for developers trying to debug environment-specific behavior differences.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search