1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
"""
pint.facets.plain.unit
~~~~~~~~~~~~~~~~~~~~~
: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, Union
from ..._typing import UnitLike
from ...compat import NUMERIC_TYPES
from ...errors import DimensionalityError
from ...util import PrettyIPython, SharedRegistryObject, UnitsContainer
from .definitions import UnitDefinition
if TYPE_CHECKING:
from pint import Context
class PlainUnit(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 pint import _unpickle_unit
return _unpickle_unit, (PlainUnit, 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, PlainUnit):
self._units = units._units
else:
raise TypeError(
"units must be of type str, Unit or "
"UnitsContainer; not {}.".format(type(units))
)
def __copy__(self) -> PlainUnit:
ret = self.__class__(self._units)
return ret
def __deepcopy__(self, memo) -> PlainUnit:
ret = self.__class__(copy.deepcopy(self._units, memo))
return ret
def __str__(self) -> str:
return " ".join(k if v == 1 else f"{k} ** {v}" for k, v in self._units.items())
def __bytes__(self) -> bytes:
return str(self).encode(locale.getpreferredencoding())
def __repr__(self) -> str:
return "<Unit('{}')>".format(self._units)
@property
def dimensionless(self) -> bool:
"""Return True if the PlainUnit is dimensionless; False otherwise."""
return not bool(self.dimensionality)
@property
def dimensionality(self) -> UnitsContainer:
"""
Returns
-------
dict
Dimensionality of the PlainUnit, 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 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, PlainUnit 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 PlainQuantity
if contexts or self._REGISTRY._active_ctx:
try:
(1 * self).to(other, *contexts, **ctx_kwargs)
return True
except DimensionalityError:
return False
if isinstance(other, (PlainQuantity, PlainUnit)):
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 PlainUnit 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) -> "PlainUnit":
if isinstance(other, NUMERIC_TYPES):
return self.__class__(self._units**other)
else:
mess = "Cannot power PlainUnit by {}".format(type(other))
raise TypeError(mess)
def __hash__(self) -> int:
return self._units.__hash__()
def __eq__(self, other) -> bool:
# We compare to the plain class of PlainUnit because each PlainUnit 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, (PlainUnit, 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))
@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)
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
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
|