"""
pint.unit
~~~~~~~~~
Functions and classes related to unit definitions and conversions.
:copyright: 2016 by Pint Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
import copy
import locale
import operator
from numbers import Number
from .compat import NUMERIC_TYPES, is_upcast_type
from .definitions import UnitDefinition
from .formatting import siunitx_format_unit
from .util import PrettyIPython, SharedRegistryObject, UnitsContainer
[docs]class Unit(PrettyIPython, SharedRegistryObject):
"""Implements a class to describe a unit supporting math operations."""
#: Default formatting string.
default_format = ""
def __reduce__(self):
# See notes in Quantity.__reduce__
from . import _unpickle
return _unpickle, (Unit, self._units)
def __init__(self, units):
super().__init__()
if isinstance(units, (UnitsContainer, UnitDefinition)):
self._units = units
elif isinstance(units, str):
self._units = self._REGISTRY.parse_units(units)._units
elif isinstance(units, Unit):
self._units = units._units
else:
raise TypeError(
"units must be of type str, Unit or "
"UnitsContainer; not {}.".format(type(units))
)
self.__used = False
self.__handling = None
@property
def debug_used(self):
return self.__used
def __copy__(self):
ret = self.__class__(self._units)
ret.__used = self.__used
return ret
def __deepcopy__(self, memo):
ret = self.__class__(copy.deepcopy(self._units, memo))
ret.__used = self.__used
return ret
def __str__(self):
return format(self)
def __bytes__(self):
return str(self).encode(locale.getpreferredencoding())
def __repr__(self):
return "<Unit('{}')>".format(self._units)
def __format__(self, spec):
spec = spec or self.default_format
# special cases
if "Lx" in spec: # the LaTeX siunitx code
return r"\si[]{%s}" % siunitx_format_unit(self)
if "~" in spec:
if not self._units:
return ""
units = UnitsContainer(
dict(
(self._REGISTRY._get_symbol(key), value)
for key, value in self._units.items()
)
)
spec = spec.replace("~", "")
else:
units = self._units
if "H" in spec:
# HTML / Jupyter Notebook
return r"\[" + format(units, spec).replace(" ", r"\ ") + r"\]"
return format(units, spec)
def format_babel(self, spec="", **kwspec):
spec = spec or self.default_format
if "~" in spec:
if self.dimensionless:
return ""
units = UnitsContainer(
dict(
(self._REGISTRY._get_symbol(key), value)
for key, value in self._units.items()
)
)
spec = spec.replace("~", "")
else:
units = self._units
return "%s" % (units.format_babel(spec, **kwspec))
@property
def dimensionless(self):
"""Return True if the Unit is dimensionless; False otherwise.
"""
return not bool(self.dimensionality)
@property
def dimensionality(self):
"""
Returns
-------
dict
Dimensionality of the Unit, e.g. ``{length: 1, time: -1}``
"""
try:
return self._dimensionality
except AttributeError:
dim = self._REGISTRY._get_dimensionality(self._units)
self._dimensionality = dim
return self._dimensionality
def compatible_units(self, *contexts):
if contexts:
with self._REGISTRY.context(*contexts):
return self._REGISTRY.get_compatible_units(self)
return self._REGISTRY.get_compatible_units(self)
def __mul__(self, other):
if self._check(other):
if isinstance(other, self.__class__):
return self.__class__(self._units * other._units)
else:
qself = self._REGISTRY.Quantity(1.0, self._units)
return qself * other
if isinstance(other, Number) and other == 1:
return self._REGISTRY.Quantity(other, self._units)
return self._REGISTRY.Quantity(1, self._units) * other
__rmul__ = __mul__
def __truediv__(self, other):
if self._check(other):
if isinstance(other, self.__class__):
return self.__class__(self._units / other._units)
else:
qself = 1.0 * self
return qself / other
return self._REGISTRY.Quantity(1 / other, self._units)
def __rtruediv__(self, other):
# As Unit and Quantity both handle truediv with each other rtruediv can
# only be called for something different.
if isinstance(other, NUMERIC_TYPES):
return self._REGISTRY.Quantity(other, 1 / self._units)
elif isinstance(other, UnitsContainer):
return self.__class__(other / self._units)
else:
return NotImplemented
__div__ = __truediv__
__rdiv__ = __rtruediv__
def __pow__(self, other):
if isinstance(other, NUMERIC_TYPES):
return self.__class__(self._units ** other)
else:
mess = "Cannot power Unit by {}".format(type(other))
raise TypeError(mess)
def __hash__(self):
return self._units.__hash__()
def __eq__(self, other):
# We compare to the base class of Unit because each Unit class is
# unique.
if self._check(other):
if isinstance(other, self.__class__):
return self._units == other._units
else:
return other == self._REGISTRY.Quantity(1, self._units)
elif isinstance(other, NUMERIC_TYPES):
return other == self._REGISTRY.Quantity(1, self._units)
else:
return self._units == other
def __ne__(self, other):
return not (self == other)
def compare(self, other, op):
self_q = self._REGISTRY.Quantity(1, self)
if isinstance(other, NUMERIC_TYPES):
return self_q.compare(other, op)
elif isinstance(other, (Unit, UnitsContainer, dict)):
return self_q.compare(self._REGISTRY.Quantity(1, other), op)
else:
return NotImplemented
__lt__ = lambda self, other: self.compare(other, op=operator.lt)
__le__ = lambda self, other: self.compare(other, op=operator.le)
__ge__ = lambda self, other: self.compare(other, op=operator.ge)
__gt__ = lambda self, other: self.compare(other, op=operator.gt)
def __int__(self):
return int(self._REGISTRY.Quantity(1, self._units))
def __float__(self):
return float(self._REGISTRY.Quantity(1, self._units))
def __complex__(self):
return complex(self._REGISTRY.Quantity(1, self._units))
__array_priority__ = 17
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
if method != "__call__":
# Only handle ufuncs as callables
return NotImplemented
# Check types and return NotImplemented when upcast type encountered
types = set(
type(arg)
for arg in list(inputs) + list(kwargs.values())
if hasattr(arg, "__array_ufunc__")
)
if any(is_upcast_type(other) for other in types):
return NotImplemented
# Act on limited implementations by conversion to multiplicative identity
# Quantity
if ufunc.__name__ in ("true_divide", "divide", "floor_divide", "multiply"):
return ufunc(
*tuple(
self._REGISTRY.Quantity(1, self._units) if arg is self else arg
for arg in inputs
),
**kwargs,
)
else:
return NotImplemented
@property
def systems(self):
out = set()
for uname in self._units.keys():
for sname, sys in self._REGISTRY._systems.items():
if uname in sys.members:
out.add(sname)
return frozenset(out)
[docs] def from_(self, value, strict=True, name="value"):
"""Converts a numerical value or quantity to this unit
Parameters
----------
value :
a Quantity (or numerical value if strict=False) to convert
strict :
boolean to indicate that only quanities are accepted (Default value = True)
name :
descriptive name to use if an exception occurs (Default value = "value")
Returns
-------
type
The converted value as this unit
"""
if self._check(value):
if not isinstance(value, self._REGISTRY.Quantity):
value = self._REGISTRY.Quantity(1, value)
return value.to(self)
elif strict:
raise ValueError("%s must be a Quantity" % value)
else:
return value * self
[docs] def m_from(self, value, strict=True, name="value"):
"""Converts a numerical value or quantity to this unit, then returns
the magnitude of the converted value
Parameters
----------
value :
a Quantity (or numerical value if strict=False) to convert
strict :
boolean to indicate that only quanities are accepted (Default value = True)
name :
descriptive name to use if an exception occurs (Default value = "value")
Returns
-------
type
The magnitude of the converted value
"""
return self.from_(value, strict=strict, name=name).magnitude
_Unit = Unit
def build_unit_class(registry):
class Unit(_Unit):
_REGISTRY = registry
return Unit