Collection Methods
Attributes that have a type subclassing from MutableSequence, MutableMapping
or MutableSet (see collections.abc in the standard library; and note that
these include the standard list, dict and set datatypes) are considered to
be collections. In addition to the scalar helper methods, spec-classes also
adds three more helper methods to assist with the mutation of elements of the
collection; again with a view to simplifying incremental setting of the
attributes and/or adoption of a copy-on-write workflow. The helper methods are:
with_<attr_singular>(...)update_<attr_singular>(...)transform_<attr_singular>(...)without_<attr_sigular>(...)
The concrete signatures differ based on the collection type, and so will be explored in more detail below. As for the scalar methods, when the collected item type is a spec-class, additional fields are added to the methods to allow direct access or mutation of attributes of the elements in the collection.
Method "Singular" Naming¶
As indicated above, the collection methods are named after the singular form of the attribute name. The generation of the singular form is handled
by the excellent inflect library.
This approach has two main benefits: 1. It clearly distinguishes the target of the collection from the scalar helper methods. 2. It encourages sensible naming of collections (i.e. plural names).
If inflect cannot find a singular form either because the attribute name was
not plural, or the plural form is the same as the singular form, or the plural
form overlaps with an existing attribute, then a suffix of _item will be used
instead. If the resulting singular form overlaps with an existing attribute, a
RuntimeError is raised and the user is invited to remedy the problem by
renaming the attribute(s).
By way of example, here are a few mappings:
| attribute name | singular form |
|---|---|
| numbers | number |
| children | child |
| errata | erratum |
| collection | collection_item |
Mutable Sequences¶
The helper method signatures for mutable sequences (including list) are:
with_<attr_singular>(_item, *, _index=MISSING, _insert=False, _inplace=False, _if=True): Adds an element to the collection when_ifisTrue. If_indexis provided, the item at that index is replaced unless_insertisTruein which case the new item is inserted before it. If_inplaceisTruethen the current instance is mutated, otherwise the mutation happens on a copy.update_<attr_singular>(_value_or_index, _new_item=MISSING, *, _by_index=MISSING, _inplace=False, _if=True): Updates/replaces an element of the collection. Indexing semantics are as fortransform_<attr_singular>below.-
transform_<attr_singular>(_value_or_index, _transform, *, _by_index=MISSING, _inplace=False, _if=True): Transforms an element of the collection if_ifisTrue. The item to be transformed is passed to the callable_transform, and is:- if
_by_indexisFalse, the first instance of_value_or_indexin the collection. - if
_by_indexisTrue, the item at index_value_or_index.
If
_by_indexis not specified, it defaults toTrueif_value_or_indexis not of the same type as as that contained in the sequence. If_inplaceisTruethen the current instance is mutated, otherwise the mutation happens on a copy. -without_<attr_singular>(_value_or_index, *, _by_index: bool = False, _inplace: bool = False, _if=True): Removes an element from the collection if_ifisTrue. The item to be removed is:- if
_by_indexisFalse, the first instance of_value_or_indexin the collection. - if
_by_indexisTrue, the item at index_value_or_index.
If
_by_indexis not specified, it defaults toTrueif_value_or_indexis not of the same type as as that contained in the sequence. If_inplaceisTruethen the current instance is mutated, otherwise the mutation happens on a copy. - if
@spec_class
class FavoriteNumbers:
numbers: List[int] = []
assert FavoriteNumbers().with_number(10).without_number(10).numbers == []
Mutable Mappings¶
The helper method signatures for mutable mappings (including dict) are:
with_<attr_singular>(_key, _value, *, _inplace=False, _if=True): Adds an element to the collection when_ifisTrueby setting the value assigned to key_keyto value_value. If_inplaceisTruethen the current instance is mutated, otherwise the mutation happens on a copy.update_<attr_singular>(_key, _new_item=MISSING, *, _inplace=False, _if=True): Updates/replaces an element of the collection when_ifisTrue.If_inplaceisTruethen the current instance is mutated, otherwise the mutation happens on a copy.transform_<attr_singular>(_key, _transform, *, _inplace=False, _if=True): Transforms the value associated with key_keyby passing it through the callable_transformwhen_ifisTrue. If_inplaceisTruethen the current instance is mutated, otherwise the mutation happens on a copy.without_<attr_singular>(_key, *, _inplace: bool = False, _if=True): Removes the mapping for_keyfrom the collection when_ifisTrue. If_inplaceisTruethen the current instance is mutated, otherwise the mutation happens on a copy.
@spec_class
class ExaminationScores:
scores: Dict[str, float] = {}
assert (
ExaminationScores()
.with_score("Peter", 10.1)
.with_score("Justine", 13.3)
.without_score("Peter")
.scores
) == {'Peter': 10.1}
Mutable Sets¶
The helper method signatures for mutable sets (including set) are:
with_<attr_singular>(_item, *, _inplace=False, _if=True): Adds the nominated element_itemto the collection when_ifisTrue. If_inplaceisTruethen the current instance is mutated, otherwise the mutation happens on a copy.update_<attr_singular>(_item, _new_item=MISSING, *, _inplace=False, _if=True): Updates/replaces an element of the collection when_ifisTrue. If_inplaceisTruethen the current instance is mutated, otherwise the mutation happens on a copy.transform_<attr_singular>(_item, _transform, *, _inplace=False, _if=True): Transforms the nominated_itemby passing it through the callable_transformwhen_ifisTrue. If_inplaceisTruethen the current instance is mutated, otherwise the mutation happens on a copy.without_<attr_singular>(_item, *, _inplace: bool = False, _if=True): Removes_itemfrom the collection when_ifisTrue. If_inplaceisTruethen the current instance is mutated, otherwise the mutation happens on a copy.
@spec_class
class FavoriteNumbers:
numbers: Set[int]
assert (
FavoriteNumbers()
.with_number(1)
.with_number(2)
.transform_number(1, lambda x: x+1)
.without_number(2)
.numbers
) == set()
Extensions for spec-class types¶
Just as for the scalar methods, if the item stored within any of the collections
above is a spec-class, then the .with_<attr_singular>() and
.transform_<attr_singular>() method signatures are extended with to allow
direct mutation of the attributes of the nested spec classes. The following
example is for sequence containers but the logic is the same for all
collections.
@spec_class
class Box:
label: str
items: List[str]
children: List["Box"]
help(Box().with_child)
# with_child(_item: __main__.Box = MISSING, *, _index: int = MISSING, _insert: bool = False, _inplace: bool = False, _if: bool = True, label: str = None, items: List[str] = None, children: List[__main__.Box] = None) method of __main__.Box instance
# Return a `Box` instance identical to this one except with an item added to or updated in `children`.
#
# Args:
# _item: A new `Box` instance for children.
# _index: Index for which to insert or replace, depending on `insert`;
# if not provided, append.
# _insert: Insert item before children[index], otherwise replace this
# index.
# _inplace: Whether to perform change without first copying.
# _if: This action is only taken when `_if` is `True`. If it is `False`,
# this is a no-op.
# label: An optional new value for `child.label`.
# items: An optional new value for `child.items`.
# children: An optional new value for `child.children`.
# Returns:
# A reference to the mutated `Box` instance.
help(Box().transform_child)
# transform_child(_value_or_index: Union[__main__.Box, int], _transform: Callable = MISSING, *, _by_index: bool = MISSING, _inplace: bool = False, _if: bool = True, label: str = None, items: List[str] = None, childre
# n: List[__main__.Box] = None) method of __main__.Box instance
# Return a `Box` instance identical to this one except with an item transformed in `children`.
#
# Args:
# _value_or_index: The value to transform, or (if `by_index=True`) its
# index.
# _transform: A function that takes the old item as input, and returns
# the new item.
# _by_index: If True, value_or_index is the index of the item to
# transform.
# _inplace: Whether to perform change without first copying.
# _if: This action is only taken when `_if` is `True`. If it is `False`,
# this is a no-op.
# label: An optional transformer for `child.label`.
# items: An optional transformer for `child.items`.
# children: An optional transformer for `child.children`.
# Returns:
# A reference to the mutated `Box` instance.