Persistent/Functional/Immutable Data Structures

raw JSON →
0.20.0 verified Tue May 12 auth: no python install: verified quickstart: stale

Pyrsistent is a Python library providing persistent and immutable data structures, such as lists, dictionaries, and sets, that facilitate functional programming paradigms. Unlike mutable data structures, all operations that would typically modify a `pyrsistent` object instead return a new, updated copy, leaving the original untouched. This simplifies reasoning about program state by eliminating hidden side effects. The library is currently at version 0.20.0, released in October 2023, and is actively maintained.

pip install pyrsistent
error TypeError: 'pvector' object does not support item assignment
cause Users attempt to modify a `pvector` in-place using item assignment (`pv[idx] = value`), which is not allowed because `pvector` is an immutable data structure.
fix
Use the set method, which returns a new pvector with the updated value at the specified index. Example: new_pv = pv.set(1, 10)
error TypeError: 'pmap' object does not support item assignment
cause Users attempt to modify a `pmap` in-place using item assignment (`pm[key] = value`), which is not allowed because `pmap` is an immutable data structure.
fix
Use the set method, which returns a new pmap with the updated value for the specified key. Example: new_pm = pm.set('b', 20)
error AttributeError: 'pvector' object has no attribute 'append'
cause Users try to use mutable list methods like `append` in-place on a `pvector` object, which is immutable and returns new instances on modification.
fix
Use the append method of pvector, which returns a new pvector with the added element. Example: new_pv = pv.append(3)
error TypeError: 'pset' object is not subscriptable
cause Users attempt to access elements of a `pset` using indexing (`ps[index]`), which is not supported as sets are unordered and do not allow indexed access.
fix
To check for membership, use the in operator (e.g., if value in ps:). If indexed access is required, convert the pset to a list first (e.g., list(ps)[0]).
breaking The behavior of `freeze` and `thaw` functions changed in a past update (related to issue #209). They now recursively convert `pyrsistent` data structures into Python built-in types and vice-versa. To retain the *old* (less recursive) behavior, you must explicitly pass `strict=False`.
fix Review calls to `freeze()` and `thaw()`. If deep recursion is undesired, pass `strict=False` (e.g., `freeze(my_pyrsistent_obj, strict=False)`).
breaking The behavior of `PMap.remove()` was changed in version 0.7.0. It now raises a `KeyError` if the element to be removed is not present. The `PMap.discard()` method was introduced as an alternative that returns the original `PMap` instance if the element is not found, aligning with `PSet`'s behavior.
fix Replace `PMap.remove(key)` with `PMap.discard(key)` if you want to avoid `KeyError` when a key might not exist, or ensure you handle the `KeyError` if `remove` is used.
breaking The methods `PMap.merge()` and `PMap.merge_with()` were deprecated and subsequently removed. They were renamed to `PMap.update()` and `PMap.update_with()` respectively.
fix Replace calls to `merge()` with `update()` and `merge_with()` with `update_with()`.
gotcha Pyrsistent data structures are fundamentally immutable. Any method that appears to 'modify' a structure (e.g., `append`, `set`, `remove`) actually returns a *new* instance with the changes, leaving the original unchanged. Attempting to treat them as mutable will lead to unexpected results where the original object appears not to have changed.
fix Always assign the result of modification operations to a new variable or back to the original variable to capture the new state (e.g., `my_vec = my_vec.append(item)`). Understand that you are always creating new versions, not altering existing ones.
gotcha For scenarios requiring many sequential updates to a `pyrsistent` collection where intermediate states are not needed, using an 'Evolver' (`pvector.evolver()`, `pmap.evolver()`, `pset.evolver()`) can be more efficient. Evolvers provide a mutable view for temporary, batch updates before finalizing to a new persistent structure.
fix Wrap sequences of temporary mutations in an evolver: `with my_pvector.evolver() as e: e[idx] = val; e.append(item); new_pvector = e.persistent()`.
gotcha PRecords require fields to be explicitly defined as part of their class definition. Attempting to initialize or set a field that was not declared will raise an `AttributeError`.
fix Ensure all fields you intend to use with a `PRecord` are explicitly declared in its class definition using `field()` or `_fields`. For example: `class MyRecord(PRecord): name = field(); age = field()`.
breaking PRecord instances must be initialized only with fields explicitly defined using `PRecord.field()` in the subclass. Passing keyword arguments for fields not declared in the `PRecord` definition will raise an `AttributeError`.
fix Ensure that all fields intended to be set during `PRecord` instantiation are explicitly declared in the `PRecord` subclass using `PRecord.field()`. Alternatively, review the instantiation call to only pass declared fields.
python os / libc status wheel install import disk
3.10 alpine (musl) wheel - 0.03s 18.2M
3.10 alpine (musl) - - 0.03s 18.2M
3.10 slim (glibc) wheel 1.6s 0.02s 19M
3.10 slim (glibc) - - 0.02s 19M
3.11 alpine (musl) wheel - 0.06s 20.1M
3.11 alpine (musl) - - 0.07s 20.1M
3.11 slim (glibc) wheel 1.6s 0.05s 21M
3.11 slim (glibc) - - 0.05s 21M
3.12 alpine (musl) wheel - 0.06s 11.9M
3.12 alpine (musl) - - 0.06s 11.9M
3.12 slim (glibc) wheel 1.5s 0.06s 13M
3.12 slim (glibc) - - 0.06s 13M
3.13 alpine (musl) wheel - 0.05s 11.7M
3.13 alpine (musl) - - 0.05s 11.5M
3.13 slim (glibc) wheel 1.5s 0.05s 12M
3.13 slim (glibc) - - 0.05s 12M
3.9 alpine (musl) wheel - 0.03s 17.7M
3.9 alpine (musl) - - 0.03s 17.7M
3.9 slim (glibc) wheel 1.8s 0.02s 18M
3.9 slim (glibc) - - 0.03s 18M

This quickstart demonstrates the creation and immutable 'evolution' of `PVector` (list-like), `PMap` (dict-like), and `PRecord` (object-like) instances. Operations like `append`, `set`, or `discard` always return a new instance with the changes, ensuring the original data structure's integrity.

from pyrsistent import pvector, pmap, PRecord

# Create a persistent vector (list-like)
v1 = pvector([1, 2, 3])
v2 = v1.append(4) # Returns a new pvector, v1 remains unchanged
v3 = v2.set(1, 5)  # Replaces element at index 1

print(f"Original vector: {v1}") # Expected: pvector([1, 2, 3])
print(f"Appended vector: {v2}") # Expected: pvector([1, 2, 3, 4])
print(f"Modified vector: {v3}") # Expected: pvector([1, 5, 3, 4])

# Create a persistent map (dict-like)
m1 = pmap({'a': 1, 'b': 2})
m2 = m1.set('c', 3) # Returns a new pmap, m1 remains unchanged
m3 = m2.set('a', 5) # Updates value for key 'a'

print(f"Original map: {m1}") # Expected: pmap({'a': 1, 'b': 2})
print(f"Appended map: {m2}") # Expected: pmap({'a': 1, 'b': 2, 'c': 3})
print(f"Modified map: {m3}") # Expected: pmap({'a': 5, 'b': 2, 'c': 3})

# Define a persistent record
class User(PRecord):
    name = None
    age = None

user1 = User(name='Alice', age=30)
user2 = user1.set(age=31) # Returns a new User record

print(f"Original user: {user1}") # Expected: User(name='Alice', age=30)
print(f"Updated user: {user2}") # Expected: User(name='Alice', age=31)