The pyrsistent-mutable package presents a decorator that will transform a decorated function to use the pyrsistent API.
This means that a set of specific operations are transformed:
- Construction of literal
set
s,dict
s andlist
s are transformed into calls topset
,pvector
andpmap
. - Assignments are rewritten to handle:
- Assignments to attributes become evolve calls; nesting is handled correctly.
- Augmented assignments are transformed into regular assignments.
- Standalone method invocations are transformed into assignments.
from pyrsistent_mutable import pyrmute
from pyrsistent import PRecord, field
class Simple(PRecord):
attr = field()
other = field()
@pyrmute
def example_func():
# Built in referential integrity
save_vector = my_vector = [0, 1, 2, 3, 4] # Mapped to a pvector
del my_vector[3] # Does *not* change save_vector
# Evolve nested attributes
my_precord = Simple(attr=Simple(), other=[])
my_precord.attr.attr = 5
my_precord.other.append(20)
# Transforms literals and comprehensions
my_maps = [{'filling': key} for key in ('apple', 'banana')]
my_maps[0]['crust'] = 'flaky'
return my_vector, save_vector, my_precord, my_maps
This example is tested in tests/test_readme.py
.
It's entirely that the imperative form is easier to read, and that pyrsistent's API is tedious for nested collections, at least compared to native Python syntax.
Also, I'm working on a language that uses this technique more extensively, so this was an opportunity to turn a prototype into something more generally useful.
Installation should just be:
# Install via pip, preferred.
pip3 install pyrsistent-mutable
# Install traditionally.
python3 setup.py install
Beyond the example shown above, the main things to keep in mind when using this module:
- You function still needs to return values.
- A "copy" can be made by simple assignment.
- Lists, dicts and sets literals and comprehensions are transformed.
- Tuples are not transformed, nor are generators.
- Method calls are only transformed if they are standalone expressions.
- Rewritten operations should fall back to normal behavior for non-pyrsistent values.
- The decorated function can't allow
nonlocal
names. global
may not work.
This is really just trying to take a prototype and do something useful with it.
If a function isn't calling something in a useful manner, the culprit is probably my very lame implementations in
pyrsistent_mutable.globals
.
This only munges assignments and expression statements.
The transformed code is written into your function under __source__
which may be helpful in debugging.
Most of these are because I've only done very preliminary work to map imperative operations to pyrsistent values.
- Assignment of slices uses the evolver framework, which doesn't handle complex slices.
- Deletion of slices similarly doesn't work.
- Augmented assignment generally requires a pyrsistent value on the rhs.
- This is mitigated now that the module translates literals.
- It is not tested on asynchronous functions or generators. It shouldn't care about them, though.
- It's all or nothing.
- The top level function can't have
nonlocal
names. Embedded functions can, though.
By default, the decorator will write the transformed source to your function as __source__
. I just pulled that name
out my hat. You can call the decorator with write_source=False
to disable this.
pip install twine
python setup.py bdist_wheel
twine upload dist/pyrsistent_mutable-0.0.x-py3-none-any.whl