Source code for yarp.value

"""
The fundamental :py:class:`Value` type for yarp, along with relatively
generic low-level utilities for creating and manipulating them.
"""


import functools
import sentinel

__names__ = [
    "NoValue",
    "Value",
    "value_list",
    "value_tuple",
    "value_dict",
    "ensure_value",
    "make_instantaneous",
    "make_persistent",
]


NoValue = sentinel.create("NoValue")
"""
A special value indicating that a ``yarp`` value has not been assigned a value.
"""

[docs]class Value(object): """ A continuous or instantaneous value which can be read and set. This base class defines the fundamental type in ``yarp``: the 'value'. The actual data contained by this object should be regarded as immutable with changes being made by replacing the Python object with a new one to affect changes. """ # Note to developers: The magic methods (e.g. __add__, __getattr__ and # __call__) are monkey-patched into this class in the yarp.python_operators # module. This is a little bit ugly but makes implementation substantially # easier/cleaner. def __init__(self, initial_value = NoValue): self._value = initial_value self._on_value_changed = [] @property def value(self): """ A property holding the current continuous value held by this object. If not yet set, or if this object represents only instantaneous values, this will be ``NoValue``. Setting this property sets the (continuous) contents of this value (raising the :py:meth:`on_value_changed` callback afterwards). To set the instantaneous value, see :py:meth:`set_instantaneous_value`. To change the value without raising a callback, set the :py:attr:`_value` attribute directly. This may be useful if you wish to make this Value mimic another by, in a callback function, setting :py:attr:`_value` in this Value directly from the other Value's :py:attr:`value` and calling :py:meth:`set_instantaneous_value` with the passed variable explicitly. You must always be sure to call :py:meth:`set_instantaneous_value` after changing :py:attr:`_value`. """ return self._value @value.setter def value(self, new_value): self._value = new_value self.set_instantaneous_value(new_value)
[docs] def set_instantaneous_value(self, new_value): """ Set the instantaneous value of this Value, calling the on_value_changed callbacks with the passed value but not storing it in the :py:attr:`value` property (which will remain unchanged). """ for cb in self._on_value_changed: cb(new_value)
[docs] def on_value_changed(self, cb): """ Registers ``callback`` as a callback function to be called when this value changes. The callback function will be called with a single argument: the value now held by this object. If the value is continuous, the value given as the argument will match the :py:attr:`Value.value` property. Otherwise, if this value is instantaneous, the value will not be reflected in the :py:attr:`Value.value` property. .. note:: There is no way to remove callbacks. For the moment this is an intentional restriction: if this causes you difficulties this is a good sign what you're doing is 'serious' enough that ``yarp`` is not for you. This function returns the callback passed to it making it possible to use it as a decorator if desired. """ self._on_value_changed.append(cb) return cb
def __repr__(self): return "Value({})".format(repr(self.value)) def __str__(self): return repr(self)
[docs]def value_list(list_of_values): r""" Returns a :py:class:`Value` consisting of a fixed list of other :py:class:`Values <Value>`. The returned :py:class:`Value` will change whenever one of its members does. Parameters ---------- list_of_values: [:py:class:`Value`, ...] A fixed list of :py:class:`Value`\ s. The :py:attr:`value` of this object will be an array of the underlying values. Callbacks will be raised whenever a value in the list changes. It is not possible to modify the list or set the contained values directly from this object. For instantaneous list members, the instantaneous value will be present in the version of this list passed to registered callbacks but otherwise not retained. (Typically the instantaneous values will be represented by :py:class:`NoValue` in :py:attr:`value` or in callbacks resulting from other :py:class:`Value`\ s changing. """ output_value = Value([v.value for v in list_of_values]) def element_changed(index, new_value): output_value._value[index] = list_of_values[index].value # Substitute in the instantaneous value of the changed element instantaneous_value = output_value.value.copy() instantaneous_value[index] = new_value output_value.set_instantaneous_value(instantaneous_value) for i, value in enumerate(list_of_values): value.on_value_changed(functools.partial(element_changed, i)) return output_value
[docs]def value_tuple(tuple_of_values): r""" A :py:class:`Value` consisting of a tuple of other :py:class:`Values <Value>`. Parameters ---------- tuple_of_values: (:py:class:`Value`, ...) A fixed tuple of :py:class:`Value`\ s. The :py:attr:`value` of this object will be a tuple of the underlying values. Callbacks will be raised whenever a value in the tuple changes. It is not possible to modify the tuple or set the contained values directly from this object. For instantaneous tuple members, the instantaneous value will be present in the version of this tuple passed to registered callbacks but otherwise not retained. (Typically the instantaneous values will be represented by :py:class:`NoValue` in :py:attr:`value` or in callbacks resulting from other :py:class:`Value`\ s changing. """ output_value = Value(tuple(v.value for v in tuple_of_values)) def element_changed(index, new_value): output_value._value = tuple(v.value for v in tuple_of_values) # Substitute in the instantaneous value of the changed element instantaneous_value = tuple( v.value if i != index else new_value for i, v in enumerate(tuple_of_values) ) output_value.set_instantaneous_value(instantaneous_value) for i, value in enumerate(tuple_of_values): value.on_value_changed(functools.partial(element_changed, i)) return output_value
[docs]def value_dict(dict_of_values): r""" A :py:class:`Value` consisting of a dictionary where the values (but not keys) are :py:class:`Values <Value>`. Parameters ---------- dict_of_values: {key: :py:class:`Value`, ...} A fixed dictionary of :py:class:`Value`\ s. The :py:attr:`value` of this object will be a dictionary of the underlying values. Callbacks will be raised whenever a value in the dictionary changes. It is not possible to modify the set of keys in the dictionary nor directly change the values of its elements from this object. For instantaneous dictionary members, the instantaneous value will be present in the version of this dict passed to registered callbacks but otherwise not retained. (Typically the instantaneous values will be represented by :py:class:`NoValue` in :py:attr:`value` or in callbacks resulting from other :py:class:`Value`\ s changing. """ output_value = Value({k: v.value for k, v in dict_of_values.items()}) def element_changed(key, new_value): output_value._value[key] = dict_of_values[key].value instantaneous_value = output_value.value.copy() instantaneous_value[key] = new_value output_value.set_instantaneous_value(instantaneous_value) for key, value in dict_of_values.items(): value.on_value_changed(functools.partial(element_changed, key)) return output_value
[docs]def ensure_value(value): """Ensure a variable is a :py:class:`Value` object, wrapping it accordingly if not. * If already a :py:class:`Value`, returns unmodified. * If a list, tuple or dict, applies :py:func:`ensure_value` to all contained values and returns a :py:class:`value_list`, :py:class:`value_tuple` or :py:class:`value_dict` respectively. * If any other type, wraps the variable in a continous :py:class:`Value` with the initial value set to the defined value. """ if isinstance(value, Value): return value elif isinstance(value, list): return value_list([ensure_value(v) for v in value]) elif isinstance(value, tuple): return value_tuple(tuple(ensure_value(v) for v in value)) elif isinstance(value, dict): return value_dict({k: ensure_value(v) for k, v in value.items()}) else: return Value(value)
[docs]def make_instantaneous(source_value): """ Make a persistent :py:class`Value` into an instantaneous one which 'fires' whenever the persistant value is changed. """ output_value = Value() ensure_value(source_value).on_value_changed(output_value.set_instantaneous_value) return output_value
[docs]def make_persistent(source_value, initial_value=NoValue): """ Make an instantaneous :py:class:`Value` into a persistant one, keeping the old value between changes. Initially sets the :py:class:`Value` to ``initial_value``. """ output_value = Value(initial_value) source_value = ensure_value(source_value) @source_value.on_value_changed def on_source_value_changed(new_value): output_value.value = new_value return output_value