Source code for pint.unit

"""
    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.
"""

from __future__ import annotations

import copy
import locale
import operator
from numbers import Number
from typing import TYPE_CHECKING, Any, Type, Union

from ._typing import UnitLike
from .compat import NUMERIC_TYPES, babel_parse, is_upcast_type
from .definitions import UnitDefinition
from .errors import DimensionalityError
from .formatting import extract_custom_flags, format_unit, split_format
from .util import PrettyIPython, SharedRegistryObject, UnitsContainer

if TYPE_CHECKING:
    from .context import Context


[docs]class Unit(PrettyIPython, SharedRegistryObject): """Implements a class to describe a unit supporting math operations.""" #: Default formatting string. default_format: str = "" def __reduce__(self): # See notes in Quantity.__reduce__ from . import _unpickle_unit return _unpickle_unit, (Unit, self._units) def __init__(self, units: UnitLike) -> None: 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) -> Any: return self.__used def __copy__(self) -> Unit: ret = self.__class__(self._units) ret.__used = self.__used return ret def __deepcopy__(self, memo) -> Unit: ret = self.__class__(copy.deepcopy(self._units, memo)) ret.__used = self.__used return ret def __str__(self) -> str: return format(self) def __bytes__(self) -> bytes: return str(self).encode(locale.getpreferredencoding()) def __repr__(self) -> str: return "<Unit('{}')>".format(self._units) def __format__(self, spec) -> str: _, uspec = split_format( spec, self.default_format, self._REGISTRY.separate_format_defaults ) if "~" in uspec: if not self._units: return "" units = UnitsContainer( dict( (self._REGISTRY._get_symbol(key), value) for key, value in self._units.items() ) ) uspec = uspec.replace("~", "") else: units = self._units return format_unit(units, uspec, registry=self._REGISTRY) def format_babel(self, spec="", locale=None, **kwspec: Any) -> str: spec = spec or extract_custom_flags(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 locale = self._REGISTRY.fmt_locale if locale is None else locale if locale is None: raise ValueError("Provide a `locale` value to localize translation.") else: kwspec["locale"] = babel_parse(locale) return units.format_babel(spec, registry=self._REGISTRY, **kwspec) @property def dimensionless(self) -> bool: """Return True if the Unit is dimensionless; False otherwise.""" return not bool(self.dimensionality) @property def dimensionality(self) -> UnitsContainer: """ 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)
[docs] def is_compatible_with( self, other: Any, *contexts: Union[str, Context], **ctx_kwargs: Any ) -> bool: """check if the other object is compatible Parameters ---------- other The object to check. Treated as dimensionless if not a Quantity, Unit or str. *contexts : str or pint.Context Contexts to use in the transformation. **ctx_kwargs : Values for the Context/s Returns ------- bool """ from .quantity import Quantity if contexts or self._REGISTRY._active_ctx: try: (1 * self).to(other, *contexts, **ctx_kwargs) return True except DimensionalityError: return False if isinstance(other, (Quantity, Unit)): return self.dimensionality == other.dimensionality if isinstance(other, str): return ( self.dimensionality == self._REGISTRY.parse_units(other).dimensionality ) return self.dimensionless
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, 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 * 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) -> "Unit": 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) -> int: return self._units.__hash__() def __eq__(self, other) -> bool: # 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) -> bool: return not (self == other) def compare(self, other, op) -> bool: 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) -> int: return int(self._REGISTRY.Quantity(1, self._units)) def __float__(self) -> float: return float(self._REGISTRY.Quantity(1, self._units)) def __complex__(self) -> complex: 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 quantities 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 quantities 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) -> Type[Unit]: class Unit(_Unit): _REGISTRY = registry return Unit