pyrsistent

repository·master·Indexed 24 days ago

https://github.com/tobgu/pyrsistent

A library of immutable (persistent) collections for Python, inspired by Clojure's functional data structures. It provides persistent replacements for standard Python types, including PVector (list), PMap (dict), and PSet (set), as well as PRecord and PClass for fixed-field objects. Features include nested transformations via .transform(), efficient batch updates using evolvers, and freeze/thaw utilities for converting between standard Python collections and pyrsistent types.

Tokens
1.8K
Snippets
3
Records
16
Agent score
31%

What's inside pyrsistent

  1. What is Pyrsistent and how do persistent collections work?

    master

    Pyrsistent provides a set of persistent collections (functional data structures) that are immutable.

    Instead of mutating a data structure in place, all methods that would normally perform a mutation instead return a new copy of the structure containing the requested updates. The original structure remains untouched. This prevents hidden side effects and ensures that an object remains unchanged throughout its lifetime.

    Pyrsistent is influenced by Clojure's persistent data structures and uses path copying to share common elements between the original and the new versions, making the creation of new versions efficient.

  2. Use Evolvers for efficient batch updates

    master

    An evolver provides a mutable-like view of a persistent data structure. It is useful when performing multiple updates where intermediate states are not needed, or when interacting with legacy code that expects in-place mutation.

    Workflow:

    1. Call .evolver() on a PVector, PMap, or PSet to get an evolver instance.
    2. Perform multiple mutations on the evolver.
    3. Check .is_dirty() to see if the evolver has changes compared to the original structure.
    4. Call .persistent() to produce a new immutable collection containing all the changes.

    Note: The original persistent structure remains completely untouched during this process.

    from pyrsistent import v
    
    v1 = v(1, 2, 3)
    e = v1.evolver()
    e[1] = 22
    e = e.append(4)
    
    # Produce the new persistent version
    v2 = e.persistent()
    # v1 is still v(1, 2, 3)
    # v2 is v(1, 22, 3, 4)
  3. Performance and implementation flavors

    master

    Pyrsistent offers two API-compatible implementations of PVector (which PMap and PSet are based on):

    1. C extension: Generally 2x to 20x faster than the pure Python version. This is used automatically whenever possible.
    2. Pure Python: Fully compatible with PyPy. When running under PyPy, the JIT can optimize these structures to be nearly as fast as Python's built-in mutable counterparts.

    Note that in optimization trade-offs, pyrsistent generally values speed over space.

  4. Use Pyrthon for literal syntax for persistent collections

    master

    If you prefer using literal syntax to define persistent collections in your code rather than using standard function calls, you can use Pyrthon.

    Warning: Pyrthon is experimental, unmaintained, and considered alpha software. Use it with caution.

  5. Find additional persistent data structures in Pyrsistent_extras

    master
    If the core Pyrsistent library does not contain the specific persistent data structure you need, you can check Pyrsistent_extras, which is maintained by @mingmingrr.
  6. Use PClass for immutable objects

    master

    A PClass is a Python class with a fixed set of specified fields. While it behaves similarly to a PRecord (fixed fields, type/invariant checking), it is not a PMap and is treated as a plain Python object rather than a collection.

    from pyrsistent import PClass, field
    
    class AClass(PClass):
        x = field()
    
    a = AClass(x=3)
    print(a.x) # 3
  7. Convert between Python and Pyrsistent with freeze and thaw

    master

    Use freeze and thaw to bridge the gap between standard Python mutable collections and Pyrsistent immutable collections.

    • freeze(data): Recursively converts Python lists, dicts, and sets into PVector, PMap, and PSet.
      • Use strict=False to prevent recursive conversion of nested standard collections.
    • thaw(data): The inverse of freeze. Recursively converts Pyrsistent collections back into standard Python types.
      • Use strict=False to prevent recursive thawing of nested Pyrsistent collections.
  8. Use Checked Collections for strict validation

    master

    Checked collections (CheckedPVector, CheckedPMap, CheckedPSet) are versions of the standard persistent collections that enforce type and invariant checks during every operation.

    They are useful for ensuring that complex nested structures maintain integrity. They can be converted back to standard Python collections using thaw() or serialize().

  9. Transform nested structures with .transform()

    master

    The .transform() method allows you to evolve deeply nested PMaps and PVectors using a path and a transformation function.

    Path and Matchers:

    • The path is a list of keys or indices.
    • Callables as Matchers: You can use functions in the path. If the function returns True for a specific key/index, it is considered a match.
    • ny (any): Matches anything at that level.
    • rex (regex): Matches keys based on a regular expression.
    • discard: A special transformation used to remove elements from the structure.

    If no elements match the path, the original structure is returned (identity).

  10. Use PSet for immutable sets

    master
    A PSet is a persistent, immutable replacement for a Python set. It supports the Set protocol and the Hashable protocol. Operations like add and remove return new sets rather than mutating the existing one.
  11. Use PVector for immutable sequences

    master

    A PVector is a persistent, immutable replacement for a Python list. It supports the Sequence protocol, meaning it can be used for random access, slicing, and iteration. Because it is immutable, operations like append or set do not modify the original vector but return a new one (evolution).

    Key characteristics:

    • Appends are amortized $O(1)$.
    • Random access and insertion are $O(\log_{32} n)$.
    • Supports the Hashable protocol, allowing it to be used as a key in mappings.