jztools.reference_sequence_v0#
Motivation#
Getting or setting nested object members often requires a sequence of attribute and key references such as
B.contents['obj'][2].value
This access pattern can be summarized by the following reference sequence:
Get attribute
contents.Get entry with key
'obj'Get entry with key
2.Get attribute
value.
This same reference sequence might need to be transferred (applied) to a variety of current and future objects, or it might need sent as a parameter to an algorithm. Being able to save serialize it in human-readable form for future use is also relevant.
Usage#
This module presents a natural way to define transferable, serializable reference sequences. Such sequences can be defined by applying the same pattern (e.g., everything after B. in the above exampe) to a RefSeq object:
rs = RefSeq().contents['obj'][2].value
Setting or getting a value for a given object can then be carried out using the __call__() method:
value = rs(B)
rs(B, new_value)
from jztools.reference_sequence import RefSeq
# Using reference sequences
arr = {'key1':list(range(20))}
orig = arr['key1'][1:10][0]
with_syntax1 = RefSeq()['key1'][1:10][0](arr)
with_syntax2 = RefSeq(['[key1]', slice(1,10), 0])(arr)
assert with_syntax1 == orig
assert with_syntax1 == with_syntax2
Slice sequence objects can be serialized in a human-readable format:
from xerializer import Serializer
# Serialization:
srlzr = Serializer()
rs = RefSeq()['key1'][1:10][0]
json_string = srlzr.serialize(rs)
print(json_string)
deserialized_rs = srlzr.deserialize(json_string)
assert deserialized_rs(arr) == rs(arr)
{"__type__": "RefSeq", "value": ["[key1]", {"__type__": "slice", "start": 1, "stop": 10}, 0]}
Reference sequences can also contain references to attributes that can be accessed in a natural way:
class A:
value = 'target'
class B:
contents = {'obj': [0, 1, A]}
# Accessing B.contents['obj'][2].value
rs = RefSeq().contents['obj'][2].value
assert rs(B) == 'target'
This approach to attribute access will pose a problem when referenced attribute and object attribute names collide. To minimize this occurrence, all object attributes are pre- or suffixed with _. Nonetheless, it is safer to use the a_() method:
rs = RefSeq().a_('contents')['obj'][2].a_('value')
assert rs(B) == 'target'
Reference sequences with attribute references can also be serialized. String keys and attributes are differentiated by surrounding keys with square brackets in the serialized representation:
print(srlzr.serialize(rs))
{"__type__": "RefSeq", "value": ["contents", "[obj]", 2, "value"]}
A similar syntax can be used to initialize reference sequences directly:
rs = RefSeq(["contents", "[obj]", 2, "value"])
assert rs(B) == 'target'
In this syntax, all non-string and bracket-enclosed string values are treated as keys, while non-bracket-enclosed strings are treated as attributes. Alternatively, the attribute and key helper functions a_() and k_() can be used to explicitly indicate intent for some or all entries:
from jztools.reference_sequence import a_, k_
rs = RefSeq([a_("contents"), k_("obj"), k_(2), a_("value")])
assert rs(B) == 'target'
To summarize, all the following are equivalent:
rs1 = RefSeq().contents['obj'][2].value
rs2 = RefSeq().a_('contents').k_('obj').k_(2).a_('value')
rs3 = RefSeq([a_("contents"), k_("obj"), k_(2), a_("value")])
assert rs1(B) == 'target'
assert rs2(B) == 'target'
assert rs3(B) == 'target'
Note that any serializable object can be used as a key, including string-key dictionaries and slices.
Todo
Re-organize, add ..rubric:: headers.
Add
set_examples.Add exampes for slice and dictionary keys, including serialization.
Warning
Care needs to be taken when using RefSeq objects since mis-spelled member names will not raise errors but rather return a new object with the misspelled member appended as an attribute reference. This is mitigated by the fact that only two members (RefSeq.k_() and RefSeq.a_()) should be called directly by the user. Note also that all RefSeq members begin or end with _.
Warning
Reference sequences should only be initialized from trusted sources, and in particular those initialized from user input can be harmful.
Classes
|
Serializable, transferable reference sequence. |
|
Specifies that the provided value is an attribute retrieved / set with |
|
Specifies that the provided value is a key retrieved / set with |
- class jztools.reference_sequence_v0.RefSeq(value=None)#
Bases:
objectSerializable, transferable reference sequence.
- k_(key: Any)#
Appends a key reference - same operation as
__getitem__(). Provided for consistency witha_().Keys can be appended to the reference sequence
rsusingrs = rs[new_key]orrs = rs.k_(new_key).Keys can be any object.
- a_(name: str)#
Appends an attribute reference. See also
__getattribute__().Attribute
'attr'can be appended to the reference sequencersusingrs = rs.attr(when there is no conflict withRefSeqmembers) orrs = rs.a_('attr').Attributes need to be valid python identifiers.
- __call__(*args)#
Sets or gets the
objat the reference sequence:__call__(obj)Gets the value ofobjat the reference sequence.__call__(obj, val)Sets the value ofobjat the reference sequence toval.
- class jztools.reference_sequence_v0.k_(value)#
Bases:
_RefSpecifies that the provided value is a key retrieved / set with
__getitem__/__setitem__(obj[key]syntax). When serializing this key, strings will be wrapped in square brackets and other types will be passed through.
- class jztools.reference_sequence_v0.a_(value)#
Bases:
_RefSpecifies that the provided value is an attribute retrieved / set with
getattr/setattr(obj.attributesyntax).