Skip to content
Open
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 117 additions & 41 deletions src/nitypes/scalar.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,79 @@ class Scalar(Generic[TScalar_co]):
>>> Scalar("value", "volts")
nitypes.scalar.Scalar(value='value', units='volts')

Comparing Scalar Objects
^^^^^^^^^^^^^^^^^^^^^^^^

You can compare scalar objects of compatible types and with identical units
using the standard comparison operators (`<`, `<=`, `>`, `>=`, `==`, and `!=`).

Here are some examples of comparing numeric scalar objects with identical units:

>>> voltage1 = Scalar(5.0, 'V')
>>> voltage2 = Scalar(10.0, 'V')
>>> voltage1 < voltage2
True
>>> voltage1 <= voltage2
True
>>> voltage1 > voltage2
False
>>> voltage1 >= voltage2
False
>>> voltage1 == voltage2
False
>>> voltage1 != voltage2
True
>>> voltage3 = Scalar(5.0, 'V')
>>> voltage1 == voltage3
True

Here are some examples of comparing string scalar objects without units:

>>> fruit1 = Scalar("apple")
>>> fruit2 = Scalar("banana")
>>> fruit1 < fruit2
True
>>> fruit1 <= fruit2
True
>>> fruit1 > fruit2
False
>>> fruit1 >= fruit2
False
>>> fruit1 == fruit2
False
>>> fruit1 != fruit2
True
>>> fruit3 = Scalar("apple")
>>> fruit1 == fruit3
True

If you compare scalar objects with compatible types and different units using the
equal to (`==`) operator, the result will be false.
If you use the not equal to (`!=`) operator, the result will be true.
Using other comparison operators (`<`, `<=`, `>`, `>=`) will raise a ValueError.

Here are some examples:

>>> voltage1 = Scalar(0.5, 'V')
>>> voltage2 = Scalar(500, 'mV')
>>> voltage1 == voltage2
False
>>> voltage1 != voltage2
True
>>> voltage1 < voltage2
Traceback (most recent call last):
...
ValueError: Comparing Scalar objects with different units is not permitted.

Attempting to compare Scalar objects of numeric and string types raises a TypeError:

>>> length1 = Scalar(5.0, 'meters')
>>> length2 = Scalar("five", 'meters')
>>> length1 < length2
Traceback (most recent call last):
...
TypeError: Comparing Scalar objects of numeric and string types is not permitted.

Class members
^^^^^^^^^^^^^
"""
Expand Down Expand Up @@ -128,59 +201,59 @@ def extended_properties(self) -> ExtendedPropertyDictionary:
"""
return self._extended_properties

def __eq__(self, value: object, /) -> bool:
"""Return self==value."""
if not isinstance(value, self.__class__):
def __eq__(self, other: object, /) -> bool:
"""Return self == other."""
if not isinstance(other, self.__class__):
return NotImplemented
return self.value == value.value and self.units == value.units
return self.value == other.value and self.units == other.units

def __gt__(self, value: Scalar[TScalar_co]) -> bool:
"""Return self > value."""
if not isinstance(value, self.__class__):
def __gt__(self, other: Scalar[TScalar_co], /) -> bool:
"""Return self > other."""
if not isinstance(other, self.__class__):
return NotImplemented
self._check_units_equal_for_comparison(value.units)
if isinstance(self.value, _NUMERIC) and isinstance(value.value, _NUMERIC):
return self.value > value.value # type: ignore[no-any-return,operator] # https://github.com/python/mypy/issues/19454
elif isinstance(self.value, str) and isinstance(value.value, str):
return self.value > value.value
self._check_units_equal_for_comparison(other.units)
if isinstance(self.value, _NUMERIC) and isinstance(other.value, _NUMERIC):
return self.value > other.value # type: ignore[no-any-return,operator] # https://github.com/python/mypy/issues/19454
elif isinstance(self.value, str) and isinstance(other.value, str):
return self.value > other.value
else:
raise TypeError("Comparing Scalar objects of numeric and string types is not permitted")
raise self._comparing_numeric_and_string_not_permitted()

def __ge__(self, value: Scalar[TScalar_co]) -> bool:
"""Return self >= value."""
if not isinstance(value, self.__class__):
def __ge__(self, other: Scalar[TScalar_co], /) -> bool:
"""Return self >= other."""
if not isinstance(other, self.__class__):
return NotImplemented
self._check_units_equal_for_comparison(value.units)
if isinstance(self.value, _NUMERIC) and isinstance(value.value, _NUMERIC):
return self.value >= value.value # type: ignore[no-any-return,operator] # https://github.com/python/mypy/issues/19454
elif isinstance(self.value, str) and isinstance(value.value, str):
return self.value >= value.value
self._check_units_equal_for_comparison(other.units)
if isinstance(self.value, _NUMERIC) and isinstance(other.value, _NUMERIC):
return self.value >= other.value # type: ignore[no-any-return,operator] # https://github.com/python/mypy/issues/19454
elif isinstance(self.value, str) and isinstance(other.value, str):
return self.value >= other.value
else:
raise TypeError("Comparing Scalar objects of numeric and string types is not permitted")
raise self._comparing_numeric_and_string_not_permitted()

def __lt__(self, value: Scalar[TScalar_co]) -> bool:
"""Return self < value."""
if not isinstance(value, self.__class__):
def __lt__(self, other: Scalar[TScalar_co], /) -> bool:
"""Return self < other."""
if not isinstance(other, self.__class__):
return NotImplemented
self._check_units_equal_for_comparison(value.units)
if isinstance(self.value, _NUMERIC) and isinstance(value.value, _NUMERIC):
return self.value < value.value # type: ignore[no-any-return,operator] # https://github.com/python/mypy/issues/19454
elif isinstance(self.value, str) and isinstance(value.value, str):
return self.value < value.value
self._check_units_equal_for_comparison(other.units)
if isinstance(self.value, _NUMERIC) and isinstance(other.value, _NUMERIC):
return self.value < other.value # type: ignore[no-any-return,operator] # https://github.com/python/mypy/issues/19454
elif isinstance(self.value, str) and isinstance(other.value, str):
return self.value < other.value
else:
raise TypeError("Comparing Scalar objects of numeric and string types is not permitted")
raise self._comparing_numeric_and_string_not_permitted()

def __le__(self, value: Scalar[TScalar_co]) -> bool:
"""Return self <= value."""
if not isinstance(value, self.__class__):
def __le__(self, other: Scalar[TScalar_co], /) -> bool:
"""Return self <= other."""
if not isinstance(other, self.__class__):
return NotImplemented
self._check_units_equal_for_comparison(value.units)
if isinstance(self.value, _NUMERIC) and isinstance(value.value, _NUMERIC):
return self.value <= value.value # type: ignore[no-any-return,operator] # https://github.com/python/mypy/issues/19454
elif isinstance(self.value, str) and isinstance(value.value, str):
return self.value <= value.value
self._check_units_equal_for_comparison(other.units)
if isinstance(self.value, _NUMERIC) and isinstance(other.value, _NUMERIC):
return self.value <= other.value # type: ignore[no-any-return,operator] # https://github.com/python/mypy/issues/19454
elif isinstance(self.value, str) and isinstance(other.value, str):
return self.value <= other.value
else:
raise TypeError("Comparing Scalar objects of numeric and string types is not permitted")
raise self._comparing_numeric_and_string_not_permitted()

def __reduce__(self) -> tuple[Any, ...]:
"""Return object state for pickling."""
Expand Down Expand Up @@ -217,6 +290,9 @@ def __str__(self) -> str:
return value_str

def _check_units_equal_for_comparison(self, other_units: str) -> None:
"""Raise a ValueError if other_units != self.units."""
"""Raise a ValueError if self.units != other_units."""
if self.units != other_units:
raise ValueError("Comparing Scalar objects with different units is not permitted.")

def _comparing_numeric_and_string_not_permitted(self) -> TypeError:
return TypeError("Comparing Scalar objects of numeric and string types is not permitted.")