Scalar Methods
Every attributed managed by spec-classes is furnished with generated scalar helper methods that simplify incremental mutation of the attributes and adoption of a copy-on-write workflow.
The methods and their signatures are:
with_<attr>(_new_value, *, _inplace=False, _if=True)
: Sets<attr>
to_new_value
if_if
isTrue
. If_inplace
isTrue
, then the value is mutated on the current instance.update_<attr>(_new_value, *, _inplace=False, _if=True)
: Updates an instance (this differs fromwith_<attr>
in that spec-classes will be incrementally updated rather than replaced by this method).transform_<attr>(_new_value, *, _inplace=False, _if=True)
: If_if
isTrue
, applies a function to the current value of<attr>
, stores the result as the new value for<attr>
on a copy of the spec class instance. If_inplace
isTrue
, then the value is mutated on the current instance.reset_<attr>(_new_value, *, _inplace=False, _if=True)
: Resets the attribute back to the default value provided by the class (orMISSING
if there is no default), if_if
isTrue
. If_inplace
isTrue
, then the value is mutated on the current instance.
For example:
@spec_class
class Box:
width: float
height: float
depth: float
color: str = 'blue'
@property
def volume(self):
return self.width * self.height * self.depth
box = Box(color='red')
box # Box(width=MISSING, height=MISSING, depth=MISSING, color='red')
box_modified = box.with_width(10.).with_height(10.).with_depth(10.)
box_modified # Box(width=10.0, height=10.0, depth=10.0, color='red')
box_modified.volume # 1000.0
box_modified.transform_width(lambda width: width * 2).volume # 2000.0
box_modified.reset_color().color # 'blue'
assert box is not box_modified
box_modified2 = box.with_width(10., _inplace=True).with_height(10., _inplace=True).with_depth(10., _inplace=True)
assert box is box_modified2
Extensions for spec-class attributes¶
When the type of the attribute being mutated by these utility methods is also a
spec class, the with_<attr>
and transform_<attr>
methods above are extended
to allow direct mutation of the attributes of the nested spec class. For
example:
@spec_class
class Box:
width: float
height: float
depth: float
color: str
nested_box: "Box"
help(Box().with_nested_box)
# with_nested_box(_new_value: __main__.Box = MISSING, *, _inplace: bool = False, _if: bool = True, width: float = None, height: float = None, depth: float = None, color: str = None, nested_box: __main__.Box = None) method of __main__.Box instance
# Return a `Box` instance identical to this one except with `nested_box` or its attributes mutated.
#
# Args:
# _new_value: The new value for `nested_box`.
# _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.
# width: An optional new value for nested_box.width.
# height: An optional new value for nested_box.height.
# depth: An optional new value for nested_box.depth.
# color: An optional new value for nested_box.color.
# nested_box: An optional new value for nested_box.nested_box.
# Returns:
# A reference to the mutated `Box` instance.
help(Box().update_nested_box)
# update_nested_box(_new_value: Callable = MISSING, *, _inplace: bool = False, _if: bool = True, width: float = MISSING, height: float = MISSING, depth: float = MISSING, color: str = MISSING, nested_box: __main__.Box = MISSING) method of __main__.Box instance
# Return a `Box` instance identical to this one except with `nested_box` or its attributes updated.
#
# Args:
# _new_value: An optional value to replace the old value for
# `nested_box`.
# _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.
# width: An optional new value for nested_box.width.
# height: An optional new value for nested_box.height.
# depth: An optional new value for nested_box.depth.
# color: An optional new value for nested_box.color.
# nested_box: An optional new value for nested_box.nested_box.
# Returns:
# A reference to the mutated `Box` instance.
help(Box().transform_nested_box)
# transform_nested_box(_transform: Callable = MISSING, *, _inplace: bool = False, _if: bool = True, width: float = None, height: float = None, depth: float = None, color: str = None, nested_box: __main__.Box = None) method of __main__.Box instance
# Return a `Box` instance identical to this one except with `nested_box` or its attributes transformed.
#
# Args:
# _transform: A function that takes the old value for nested_box as
# input, and returns the new value.
# _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.
# width: An optional transformer for nested_box.width.
# height: An optional transformer for nested_box.height.
# depth: An optional transformer for nested_box.depth.
# color: An optional transformer for nested_box.color.
# nested_box: An optional transformer for nested_box.nested_box.
# Returns:
# A reference to the mutated `Box` instance.
(
Box()
.with_nested_box(width=10., height=10., depth=10., color='red')
.update_nested_box(width=30.)
# Using `with_nested_box` instead would reset other properties to their default values.
)
# Box(
# width=MISSING,
# height=MISSING,
# depth=MISSING,
# color=MISSING,
# nested_box=Box(
# width=30.0,
# height=10.0,
# depth=10.0,
# color='red',
# nested_box=MISSING
# )
# )