python-units 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_units-0.3.0 → python_units-0.4.0}/PKG-INFO +183 -2
- {python_units-0.3.0 → python_units-0.4.0}/README.md +182 -1
- {python_units-0.3.0 → python_units-0.4.0}/pyproject.toml +1 -1
- {python_units-0.3.0 → python_units-0.4.0}/src/api/public.py +50 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/api/si.py +51 -1
- {python_units-0.3.0 → python_units-0.4.0}/src/core/__init__.py +8 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/core/quantity.py +226 -12
- {python_units-0.3.0 → python_units-0.4.0}/src/core/unit_definitions.py +170 -17
- {python_units-0.3.0 → python_units-0.4.0}/src/python_units.egg-info/PKG-INFO +183 -2
- {python_units-0.3.0 → python_units-0.4.0}/src/units/unit.py +16 -0
- {python_units-0.3.0 → python_units-0.4.0}/LICENSE +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/setup.cfg +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/setup.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/adapters/__init__.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/api/__init__.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/core/deprecations.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/core/errors.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/models/__init__.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/models/dimension.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/python_units.egg-info/SOURCES.txt +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/python_units.egg-info/dependency_links.txt +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/python_units.egg-info/requires.txt +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/python_units.egg-info/top_level.txt +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/services/__init__.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/units/__init__.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/units/dimension.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/units/errors.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/units/quantity.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/units/si.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/utils/__init__.py +0 -0
- {python_units-0.3.0 → python_units-0.4.0}/src/utils/numbers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-units
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python library to represent numbers with units
|
|
5
5
|
Author-email: "Paul K. Korir, PhD" <paul.korir@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -30,7 +30,74 @@ Dynamic: license-file
|
|
|
30
30
|
[](https://pypi.org/project/python-units/)
|
|
31
31
|
[](/Users/paulkorir/PycharmProjects/python-units/tests/unit/test_units.py)
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
# The Price of Unitless Arithmetic
|
|
34
|
+
|
|
35
|
+
On September 23, 1999, flight controllers expected NASA's Mars Climate Orbiter
|
|
36
|
+
to pass behind Mars, fire its engine, and come back into radio contact after
|
|
37
|
+
orbit insertion. It never came back. When engineers reviewed the final hours of
|
|
38
|
+
flight data, the trajectory was not where the navigation system thought it was:
|
|
39
|
+
the spacecraft had approached Mars far lower than planned. The investigation
|
|
40
|
+
traced the loss to a unit boundary that software had failed to defend. One side
|
|
41
|
+
of the system handled "small forces" data in English units; the navigation side
|
|
42
|
+
expected metric units.
|
|
43
|
+
|
|
44
|
+
That is the kind of bug this package is meant to stop. Without units, the
|
|
45
|
+
mistake is just arithmetic:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
# A navigation routine expects impulse in newton-seconds.
|
|
49
|
+
expected_impulse_ns = 120
|
|
50
|
+
|
|
51
|
+
# A supplier routine accidentally sends a value in a different force unit.
|
|
52
|
+
# The number is still just a number, so Python accepts it.
|
|
53
|
+
reported_impulse_other_units = 120
|
|
54
|
+
|
|
55
|
+
trajectory_impulse = expected_impulse_ns + reported_impulse_other_units
|
|
56
|
+
print(trajectory_impulse) # 240
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
There is nothing in `240` that tells you a spacecraft trajectory may now be
|
|
60
|
+
wrong. With units attached, the mismatch stops at the boundary:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from units import CustomUnitBase
|
|
64
|
+
from units.dimension import DimensionSystem
|
|
65
|
+
from units.si import newton, second
|
|
66
|
+
|
|
67
|
+
class EnglishImpulseUnit(CustomUnitBase):
|
|
68
|
+
dimension_system = DimensionSystem("english-impulse", ("lbf_s",))
|
|
69
|
+
|
|
70
|
+
pound_force_second = EnglishImpulseUnit.define("lbf_s")
|
|
71
|
+
|
|
72
|
+
expected_impulse = 120 * newton * second
|
|
73
|
+
reported_impulse = 120 * pound_force_second
|
|
74
|
+
|
|
75
|
+
trajectory_impulse = expected_impulse + reported_impulse
|
|
76
|
+
# UnitCompatibilityError: units mismatch: m·kg·s^-1 and lbf_s
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
That failure is the feature. A bug that would otherwise move through a program
|
|
80
|
+
as an ordinary number is stopped before it contaminates mission-critical
|
|
81
|
+
calculations.
|
|
82
|
+
|
|
83
|
+
Background: NASA/JPL describe the Mars Climate Orbiter loss as a navigation
|
|
84
|
+
error caused by a failure to translate English units to metric, sending the
|
|
85
|
+
spacecraft too close to Mars.
|
|
86
|
+
|
|
87
|
+
Source: https://en.wikipedia.org/wiki/Mars_Climate_Orbiter
|
|
88
|
+
|
|
89
|
+
# About
|
|
90
|
+
|
|
91
|
+
`python-units` is a Python package for unit-aware arithmetic. It provides:
|
|
92
|
+
- a `Quantity` type that combines numeric values with unit information
|
|
93
|
+
- a registry of SI base and derived units
|
|
94
|
+
- algebraic unit manipulation and compatibility checks
|
|
95
|
+
- explicit multiplicative conversions between compatible units
|
|
96
|
+
- a public API that prioritizes scalar-by-unit construction and SI unit imports
|
|
97
|
+
- a migration path from the legacy `Unit` constructor and compatibility helpers
|
|
98
|
+
- a Python 3-only codebase with no Python 2 compatibility shims
|
|
99
|
+
- a project structure that separates public API, core logic, data models, and utilities
|
|
100
|
+
- comprehensive unit tests and documentation
|
|
34
101
|
|
|
35
102
|
Supported Python versions: 3.10+
|
|
36
103
|
|
|
@@ -139,12 +206,18 @@ Stable top-level imports:
|
|
|
139
206
|
|
|
140
207
|
* `Quantity`
|
|
141
208
|
* `Unit` (compatibility alias for `Quantity`)
|
|
209
|
+
* `convert`
|
|
210
|
+
* `value`
|
|
211
|
+
* `unit`
|
|
212
|
+
* `multiplier`
|
|
142
213
|
* `UnitsError`, `InvalidUnitError`, `InvalidValueError`,
|
|
143
214
|
`UnitCompatibilityError`, `UnitOperandError`
|
|
144
215
|
|
|
145
216
|
Canonical unit imports:
|
|
146
217
|
|
|
147
218
|
* `from units.si import metre, second, newton`
|
|
219
|
+
* prefixed and scaled units such as `kilometre`, `centimetre`, `gram`,
|
|
220
|
+
`minute`, `hour`, `kilowatt`, and `millivolt`
|
|
148
221
|
|
|
149
222
|
Legacy compatibility helpers:
|
|
150
223
|
|
|
@@ -166,11 +239,119 @@ deprecated compatibility paths are scheduled for removal in `1.0.0`.
|
|
|
166
239
|
|
|
167
240
|
* Addition and subtraction require identical units.
|
|
168
241
|
* Multiplication and division combine units algebraically.
|
|
242
|
+
* Explicit scale-only conversions are available through `quantity.to(unit)` and
|
|
243
|
+
`convert(quantity, unit)`.
|
|
169
244
|
* Integer powers of units and unit-bearing quantities are supported.
|
|
170
245
|
* Unitless quantities are supported explicitly.
|
|
246
|
+
* Affine conversions, such as `degree_celcius <-> kelvin`, are intentionally not
|
|
247
|
+
implemented yet.
|
|
171
248
|
* The core quantity model allows signed values. Domain-specific constraints such
|
|
172
249
|
as non-negative lengths should be enforced by higher-level types or validators.
|
|
173
250
|
|
|
251
|
+
# Conversion foundations
|
|
252
|
+
|
|
253
|
+
`0.4.0` adds explicit multiplicative conversions. Conversion never happens
|
|
254
|
+
silently during addition or subtraction; you choose the target unit.
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from units import convert, multiplier, unit, value
|
|
258
|
+
from units.si import gram, hour, kilogram, kilometre, metre, minute, second
|
|
259
|
+
|
|
260
|
+
distance = 1.5 * kilometre
|
|
261
|
+
print(distance.to(metre)) # 1500 m
|
|
262
|
+
print(convert(2500 * metre, kilometre)) # 2.5 km
|
|
263
|
+
|
|
264
|
+
duration = 2 * hour
|
|
265
|
+
print(duration.to(minute)) # 120 min
|
|
266
|
+
print((1500 * gram).to(kilogram)) # 1.5 kg
|
|
267
|
+
|
|
268
|
+
speed = (72 * kilometre) / (2 * hour)
|
|
269
|
+
print(speed) # 10.0 m·s^-1
|
|
270
|
+
|
|
271
|
+
print(value(distance)) # 1.5
|
|
272
|
+
print(unit(distance)) # km
|
|
273
|
+
print(multiplier(kilometre)) # 1000.0
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The conversion model is scale-only in this release. Celsius is a named
|
|
277
|
+
temperature unit, but converting between Celsius and kelvin requires an offset
|
|
278
|
+
and is reserved for a later affine-conversion release.
|
|
279
|
+
|
|
280
|
+
# Prefixed and scaled units
|
|
281
|
+
|
|
282
|
+
Common SI prefixes and practical time units are available from `units.si`:
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
from units.si import (
|
|
286
|
+
centimetre,
|
|
287
|
+
gram,
|
|
288
|
+
hour,
|
|
289
|
+
kiloampere,
|
|
290
|
+
kilometre,
|
|
291
|
+
kilovolt,
|
|
292
|
+
kilowatt,
|
|
293
|
+
megawatt,
|
|
294
|
+
micrometre,
|
|
295
|
+
microsecond,
|
|
296
|
+
milliampere,
|
|
297
|
+
milligram,
|
|
298
|
+
millimetre,
|
|
299
|
+
millisecond,
|
|
300
|
+
millivolt,
|
|
301
|
+
milliwatt,
|
|
302
|
+
minute,
|
|
303
|
+
nanometre,
|
|
304
|
+
nanosecond,
|
|
305
|
+
tonne,
|
|
306
|
+
)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Scaled units participate correctly in multiplication, division, and powers:
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
from units.si import hour, kilometre, metre
|
|
313
|
+
|
|
314
|
+
area = (2 * kilometre) * (3 * metre)
|
|
315
|
+
print(area) # 6000 m^2
|
|
316
|
+
|
|
317
|
+
square = (2 * kilometre) ** 2
|
|
318
|
+
print(square) # 4000000 m^2
|
|
319
|
+
|
|
320
|
+
speed = (72 * kilometre) / (2 * hour)
|
|
321
|
+
print(speed) # 10.0 m·s^-1
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
# Familiar composite units
|
|
325
|
+
|
|
326
|
+
Composite unit expressions such as `kilometre / hour` are algebraic unit
|
|
327
|
+
definitions. They carry the correct scale factor, but anonymous composite units
|
|
328
|
+
render in canonical SI base form:
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
from units.si import hour, kilometre
|
|
332
|
+
|
|
333
|
+
speed = 30 * kilometre / hour
|
|
334
|
+
print(speed) # 8.333333333333334 m·s^-1
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
When you want a semantically familiar display unit, give that composite unit an
|
|
338
|
+
explicit name and convert to it:
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
from units import DerivedUnit, convert
|
|
342
|
+
from units.si import hour, kilometre
|
|
343
|
+
|
|
344
|
+
kilometres_per_hour = DerivedUnit.define("km·hr^-1", kilometre / hour)
|
|
345
|
+
|
|
346
|
+
speed = 30 * kilometre / hour
|
|
347
|
+
print(convert(speed, kilometres_per_hour)) # 30 km·hr^-1
|
|
348
|
+
print(30 * kilometres_per_hour) # 30 km·hr^-1
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
This keeps the arithmetic deterministic while letting application code choose
|
|
352
|
+
domain-specific display names such as `km·hr^-1`, `N·m`, or any other familiar
|
|
353
|
+
derived unit form.
|
|
354
|
+
|
|
174
355
|
# Real-world examples
|
|
175
356
|
|
|
176
357
|
## Electrical engineering: from resistance to power dissipation
|
|
@@ -4,7 +4,74 @@
|
|
|
4
4
|
[](https://pypi.org/project/python-units/)
|
|
5
5
|
[](/Users/paulkorir/PycharmProjects/python-units/tests/unit/test_units.py)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# The Price of Unitless Arithmetic
|
|
8
|
+
|
|
9
|
+
On September 23, 1999, flight controllers expected NASA's Mars Climate Orbiter
|
|
10
|
+
to pass behind Mars, fire its engine, and come back into radio contact after
|
|
11
|
+
orbit insertion. It never came back. When engineers reviewed the final hours of
|
|
12
|
+
flight data, the trajectory was not where the navigation system thought it was:
|
|
13
|
+
the spacecraft had approached Mars far lower than planned. The investigation
|
|
14
|
+
traced the loss to a unit boundary that software had failed to defend. One side
|
|
15
|
+
of the system handled "small forces" data in English units; the navigation side
|
|
16
|
+
expected metric units.
|
|
17
|
+
|
|
18
|
+
That is the kind of bug this package is meant to stop. Without units, the
|
|
19
|
+
mistake is just arithmetic:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
# A navigation routine expects impulse in newton-seconds.
|
|
23
|
+
expected_impulse_ns = 120
|
|
24
|
+
|
|
25
|
+
# A supplier routine accidentally sends a value in a different force unit.
|
|
26
|
+
# The number is still just a number, so Python accepts it.
|
|
27
|
+
reported_impulse_other_units = 120
|
|
28
|
+
|
|
29
|
+
trajectory_impulse = expected_impulse_ns + reported_impulse_other_units
|
|
30
|
+
print(trajectory_impulse) # 240
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
There is nothing in `240` that tells you a spacecraft trajectory may now be
|
|
34
|
+
wrong. With units attached, the mismatch stops at the boundary:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from units import CustomUnitBase
|
|
38
|
+
from units.dimension import DimensionSystem
|
|
39
|
+
from units.si import newton, second
|
|
40
|
+
|
|
41
|
+
class EnglishImpulseUnit(CustomUnitBase):
|
|
42
|
+
dimension_system = DimensionSystem("english-impulse", ("lbf_s",))
|
|
43
|
+
|
|
44
|
+
pound_force_second = EnglishImpulseUnit.define("lbf_s")
|
|
45
|
+
|
|
46
|
+
expected_impulse = 120 * newton * second
|
|
47
|
+
reported_impulse = 120 * pound_force_second
|
|
48
|
+
|
|
49
|
+
trajectory_impulse = expected_impulse + reported_impulse
|
|
50
|
+
# UnitCompatibilityError: units mismatch: m·kg·s^-1 and lbf_s
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
That failure is the feature. A bug that would otherwise move through a program
|
|
54
|
+
as an ordinary number is stopped before it contaminates mission-critical
|
|
55
|
+
calculations.
|
|
56
|
+
|
|
57
|
+
Background: NASA/JPL describe the Mars Climate Orbiter loss as a navigation
|
|
58
|
+
error caused by a failure to translate English units to metric, sending the
|
|
59
|
+
spacecraft too close to Mars.
|
|
60
|
+
|
|
61
|
+
Source: https://en.wikipedia.org/wiki/Mars_Climate_Orbiter
|
|
62
|
+
|
|
63
|
+
# About
|
|
64
|
+
|
|
65
|
+
`python-units` is a Python package for unit-aware arithmetic. It provides:
|
|
66
|
+
- a `Quantity` type that combines numeric values with unit information
|
|
67
|
+
- a registry of SI base and derived units
|
|
68
|
+
- algebraic unit manipulation and compatibility checks
|
|
69
|
+
- explicit multiplicative conversions between compatible units
|
|
70
|
+
- a public API that prioritizes scalar-by-unit construction and SI unit imports
|
|
71
|
+
- a migration path from the legacy `Unit` constructor and compatibility helpers
|
|
72
|
+
- a Python 3-only codebase with no Python 2 compatibility shims
|
|
73
|
+
- a project structure that separates public API, core logic, data models, and utilities
|
|
74
|
+
- comprehensive unit tests and documentation
|
|
8
75
|
|
|
9
76
|
Supported Python versions: 3.10+
|
|
10
77
|
|
|
@@ -113,12 +180,18 @@ Stable top-level imports:
|
|
|
113
180
|
|
|
114
181
|
* `Quantity`
|
|
115
182
|
* `Unit` (compatibility alias for `Quantity`)
|
|
183
|
+
* `convert`
|
|
184
|
+
* `value`
|
|
185
|
+
* `unit`
|
|
186
|
+
* `multiplier`
|
|
116
187
|
* `UnitsError`, `InvalidUnitError`, `InvalidValueError`,
|
|
117
188
|
`UnitCompatibilityError`, `UnitOperandError`
|
|
118
189
|
|
|
119
190
|
Canonical unit imports:
|
|
120
191
|
|
|
121
192
|
* `from units.si import metre, second, newton`
|
|
193
|
+
* prefixed and scaled units such as `kilometre`, `centimetre`, `gram`,
|
|
194
|
+
`minute`, `hour`, `kilowatt`, and `millivolt`
|
|
122
195
|
|
|
123
196
|
Legacy compatibility helpers:
|
|
124
197
|
|
|
@@ -140,11 +213,119 @@ deprecated compatibility paths are scheduled for removal in `1.0.0`.
|
|
|
140
213
|
|
|
141
214
|
* Addition and subtraction require identical units.
|
|
142
215
|
* Multiplication and division combine units algebraically.
|
|
216
|
+
* Explicit scale-only conversions are available through `quantity.to(unit)` and
|
|
217
|
+
`convert(quantity, unit)`.
|
|
143
218
|
* Integer powers of units and unit-bearing quantities are supported.
|
|
144
219
|
* Unitless quantities are supported explicitly.
|
|
220
|
+
* Affine conversions, such as `degree_celcius <-> kelvin`, are intentionally not
|
|
221
|
+
implemented yet.
|
|
145
222
|
* The core quantity model allows signed values. Domain-specific constraints such
|
|
146
223
|
as non-negative lengths should be enforced by higher-level types or validators.
|
|
147
224
|
|
|
225
|
+
# Conversion foundations
|
|
226
|
+
|
|
227
|
+
`0.4.0` adds explicit multiplicative conversions. Conversion never happens
|
|
228
|
+
silently during addition or subtraction; you choose the target unit.
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from units import convert, multiplier, unit, value
|
|
232
|
+
from units.si import gram, hour, kilogram, kilometre, metre, minute, second
|
|
233
|
+
|
|
234
|
+
distance = 1.5 * kilometre
|
|
235
|
+
print(distance.to(metre)) # 1500 m
|
|
236
|
+
print(convert(2500 * metre, kilometre)) # 2.5 km
|
|
237
|
+
|
|
238
|
+
duration = 2 * hour
|
|
239
|
+
print(duration.to(minute)) # 120 min
|
|
240
|
+
print((1500 * gram).to(kilogram)) # 1.5 kg
|
|
241
|
+
|
|
242
|
+
speed = (72 * kilometre) / (2 * hour)
|
|
243
|
+
print(speed) # 10.0 m·s^-1
|
|
244
|
+
|
|
245
|
+
print(value(distance)) # 1.5
|
|
246
|
+
print(unit(distance)) # km
|
|
247
|
+
print(multiplier(kilometre)) # 1000.0
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The conversion model is scale-only in this release. Celsius is a named
|
|
251
|
+
temperature unit, but converting between Celsius and kelvin requires an offset
|
|
252
|
+
and is reserved for a later affine-conversion release.
|
|
253
|
+
|
|
254
|
+
# Prefixed and scaled units
|
|
255
|
+
|
|
256
|
+
Common SI prefixes and practical time units are available from `units.si`:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
from units.si import (
|
|
260
|
+
centimetre,
|
|
261
|
+
gram,
|
|
262
|
+
hour,
|
|
263
|
+
kiloampere,
|
|
264
|
+
kilometre,
|
|
265
|
+
kilovolt,
|
|
266
|
+
kilowatt,
|
|
267
|
+
megawatt,
|
|
268
|
+
micrometre,
|
|
269
|
+
microsecond,
|
|
270
|
+
milliampere,
|
|
271
|
+
milligram,
|
|
272
|
+
millimetre,
|
|
273
|
+
millisecond,
|
|
274
|
+
millivolt,
|
|
275
|
+
milliwatt,
|
|
276
|
+
minute,
|
|
277
|
+
nanometre,
|
|
278
|
+
nanosecond,
|
|
279
|
+
tonne,
|
|
280
|
+
)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Scaled units participate correctly in multiplication, division, and powers:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
from units.si import hour, kilometre, metre
|
|
287
|
+
|
|
288
|
+
area = (2 * kilometre) * (3 * metre)
|
|
289
|
+
print(area) # 6000 m^2
|
|
290
|
+
|
|
291
|
+
square = (2 * kilometre) ** 2
|
|
292
|
+
print(square) # 4000000 m^2
|
|
293
|
+
|
|
294
|
+
speed = (72 * kilometre) / (2 * hour)
|
|
295
|
+
print(speed) # 10.0 m·s^-1
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
# Familiar composite units
|
|
299
|
+
|
|
300
|
+
Composite unit expressions such as `kilometre / hour` are algebraic unit
|
|
301
|
+
definitions. They carry the correct scale factor, but anonymous composite units
|
|
302
|
+
render in canonical SI base form:
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
from units.si import hour, kilometre
|
|
306
|
+
|
|
307
|
+
speed = 30 * kilometre / hour
|
|
308
|
+
print(speed) # 8.333333333333334 m·s^-1
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
When you want a semantically familiar display unit, give that composite unit an
|
|
312
|
+
explicit name and convert to it:
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
from units import DerivedUnit, convert
|
|
316
|
+
from units.si import hour, kilometre
|
|
317
|
+
|
|
318
|
+
kilometres_per_hour = DerivedUnit.define("km·hr^-1", kilometre / hour)
|
|
319
|
+
|
|
320
|
+
speed = 30 * kilometre / hour
|
|
321
|
+
print(convert(speed, kilometres_per_hour)) # 30 km·hr^-1
|
|
322
|
+
print(30 * kilometres_per_hour) # 30 km·hr^-1
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
This keeps the arithmetic deterministic while letting application code choose
|
|
326
|
+
domain-specific display names such as `km·hr^-1`, `N·m`, or any other familiar
|
|
327
|
+
derived unit form.
|
|
328
|
+
|
|
148
329
|
# Real-world examples
|
|
149
330
|
|
|
150
331
|
## Electrical engineering: from resistance to power dissipation
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-units"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Python library to represent numbers with units"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -5,20 +5,40 @@ from api.si import (
|
|
|
5
5
|
ampere,
|
|
6
6
|
becquerel,
|
|
7
7
|
candela,
|
|
8
|
+
centimetre,
|
|
8
9
|
coulomb,
|
|
9
10
|
degree_celcius,
|
|
10
11
|
farad,
|
|
12
|
+
gram,
|
|
11
13
|
gray,
|
|
12
14
|
henry,
|
|
13
15
|
hertz,
|
|
16
|
+
hour,
|
|
14
17
|
joule,
|
|
15
18
|
katal,
|
|
16
19
|
kelvin,
|
|
17
20
|
kilogram,
|
|
21
|
+
kiloampere,
|
|
22
|
+
kilometre,
|
|
23
|
+
kilovolt,
|
|
24
|
+
kilowatt,
|
|
18
25
|
lumen,
|
|
19
26
|
lux,
|
|
27
|
+
megawatt,
|
|
20
28
|
metre,
|
|
29
|
+
microgram,
|
|
30
|
+
micrometre,
|
|
31
|
+
microsecond,
|
|
32
|
+
milliampere,
|
|
33
|
+
milligram,
|
|
34
|
+
millimetre,
|
|
35
|
+
millisecond,
|
|
36
|
+
millivolt,
|
|
37
|
+
milliwatt,
|
|
38
|
+
minute,
|
|
21
39
|
mole,
|
|
40
|
+
nanometre,
|
|
41
|
+
nanosecond,
|
|
22
42
|
newton,
|
|
23
43
|
ohm,
|
|
24
44
|
pascal,
|
|
@@ -28,6 +48,7 @@ from api.si import (
|
|
|
28
48
|
sievert,
|
|
29
49
|
steradian,
|
|
30
50
|
tesla,
|
|
51
|
+
tonne,
|
|
31
52
|
volt,
|
|
32
53
|
watt,
|
|
33
54
|
weber,
|
|
@@ -43,12 +64,16 @@ from core.quantity import (
|
|
|
43
64
|
Quantity,
|
|
44
65
|
complex_quantity,
|
|
45
66
|
complex_unit,
|
|
67
|
+
convert,
|
|
46
68
|
float_quantity,
|
|
47
69
|
float_unit,
|
|
48
70
|
int_quantity,
|
|
49
71
|
int_unit,
|
|
50
72
|
long_quantity,
|
|
51
73
|
long_unit,
|
|
74
|
+
multiplier,
|
|
75
|
+
unit,
|
|
76
|
+
value,
|
|
52
77
|
)
|
|
53
78
|
from core.unit_definitions import BaseUnit, CustomUnitBase, DerivedUnit, SIUnit
|
|
54
79
|
from models.dimension import Dimension, DimensionSystem
|
|
@@ -73,28 +98,50 @@ __all__ = [
|
|
|
73
98
|
"ampere",
|
|
74
99
|
"becquerel",
|
|
75
100
|
"candela",
|
|
101
|
+
"centimetre",
|
|
76
102
|
"complex_quantity",
|
|
77
103
|
"complex_unit",
|
|
104
|
+
"convert",
|
|
78
105
|
"coulomb",
|
|
79
106
|
"degree_celcius",
|
|
80
107
|
"farad",
|
|
81
108
|
"float_quantity",
|
|
82
109
|
"float_unit",
|
|
110
|
+
"gram",
|
|
83
111
|
"gray",
|
|
84
112
|
"henry",
|
|
85
113
|
"hertz",
|
|
114
|
+
"hour",
|
|
86
115
|
"int_quantity",
|
|
87
116
|
"int_unit",
|
|
88
117
|
"joule",
|
|
89
118
|
"katal",
|
|
90
119
|
"kelvin",
|
|
91
120
|
"kilogram",
|
|
121
|
+
"kiloampere",
|
|
122
|
+
"kilometre",
|
|
123
|
+
"kilovolt",
|
|
124
|
+
"kilowatt",
|
|
92
125
|
"long_quantity",
|
|
93
126
|
"long_unit",
|
|
94
127
|
"lumen",
|
|
95
128
|
"lux",
|
|
129
|
+
"megawatt",
|
|
96
130
|
"metre",
|
|
131
|
+
"microgram",
|
|
132
|
+
"micrometre",
|
|
133
|
+
"microsecond",
|
|
134
|
+
"milliampere",
|
|
135
|
+
"milligram",
|
|
136
|
+
"millimetre",
|
|
137
|
+
"millisecond",
|
|
138
|
+
"millivolt",
|
|
139
|
+
"milliwatt",
|
|
140
|
+
"minute",
|
|
141
|
+
"multiplier",
|
|
97
142
|
"mole",
|
|
143
|
+
"nanometre",
|
|
144
|
+
"nanosecond",
|
|
98
145
|
"newton",
|
|
99
146
|
"ohm",
|
|
100
147
|
"pascal",
|
|
@@ -104,6 +151,9 @@ __all__ = [
|
|
|
104
151
|
"sievert",
|
|
105
152
|
"steradian",
|
|
106
153
|
"tesla",
|
|
154
|
+
"tonne",
|
|
155
|
+
"unit",
|
|
156
|
+
"value",
|
|
107
157
|
"volt",
|
|
108
158
|
"watt",
|
|
109
159
|
"weber",
|
|
@@ -29,7 +29,11 @@ siemens = DerivedUnit.define("S", ampere / volt)
|
|
|
29
29
|
weber = DerivedUnit.define("Wb", volt * second)
|
|
30
30
|
tesla = DerivedUnit.define("T", weber / metre / metre)
|
|
31
31
|
henry = DerivedUnit.define("H", weber / ampere)
|
|
32
|
-
degree_celcius = DerivedUnit.define(
|
|
32
|
+
degree_celcius = DerivedUnit.define(
|
|
33
|
+
"°C",
|
|
34
|
+
kelvin,
|
|
35
|
+
supports_multiplicative_conversion=False,
|
|
36
|
+
)
|
|
33
37
|
lumen = DerivedUnit.define("lm", candela * steradian)
|
|
34
38
|
lux = DerivedUnit.define("lx", lumen / metre / metre)
|
|
35
39
|
becquerel = DerivedUnit.define("Bq", SIUnit() / second)
|
|
@@ -37,6 +41,31 @@ gray = DerivedUnit.define("Gy", joule / kilogram)
|
|
|
37
41
|
sievert = DerivedUnit.define("Sv", joule / kilogram)
|
|
38
42
|
katal = DerivedUnit.define("kat", mole / second)
|
|
39
43
|
|
|
44
|
+
kilometre = DerivedUnit.define("km", metre, conversion_factor=1000.0)
|
|
45
|
+
centimetre = DerivedUnit.define("cm", metre, conversion_factor=0.01)
|
|
46
|
+
millimetre = DerivedUnit.define("mm", metre, conversion_factor=0.001)
|
|
47
|
+
micrometre = DerivedUnit.define("µm", metre, conversion_factor=0.000001)
|
|
48
|
+
nanometre = DerivedUnit.define("nm", metre, conversion_factor=0.000000001)
|
|
49
|
+
|
|
50
|
+
gram = DerivedUnit.define("g", kilogram, conversion_factor=0.001)
|
|
51
|
+
milligram = DerivedUnit.define("mg", kilogram, conversion_factor=0.000001)
|
|
52
|
+
microgram = DerivedUnit.define("µg", kilogram, conversion_factor=0.000000001)
|
|
53
|
+
tonne = DerivedUnit.define("t", kilogram, conversion_factor=1000.0)
|
|
54
|
+
|
|
55
|
+
minute = DerivedUnit.define("min", second, conversion_factor=60.0)
|
|
56
|
+
hour = DerivedUnit.define("h", second, conversion_factor=3600.0)
|
|
57
|
+
millisecond = DerivedUnit.define("ms", second, conversion_factor=0.001)
|
|
58
|
+
microsecond = DerivedUnit.define("µs", second, conversion_factor=0.000001)
|
|
59
|
+
nanosecond = DerivedUnit.define("ns", second, conversion_factor=0.000000001)
|
|
60
|
+
|
|
61
|
+
milliampere = DerivedUnit.define("mA", ampere, conversion_factor=0.001)
|
|
62
|
+
kiloampere = DerivedUnit.define("kA", ampere, conversion_factor=1000.0)
|
|
63
|
+
millivolt = DerivedUnit.define("mV", volt, conversion_factor=0.001)
|
|
64
|
+
kilovolt = DerivedUnit.define("kV", volt, conversion_factor=1000.0)
|
|
65
|
+
milliwatt = DerivedUnit.define("mW", watt, conversion_factor=0.001)
|
|
66
|
+
kilowatt = DerivedUnit.define("kW", watt, conversion_factor=1000.0)
|
|
67
|
+
megawatt = DerivedUnit.define("MW", watt, conversion_factor=1000000.0)
|
|
68
|
+
|
|
40
69
|
for unit in (
|
|
41
70
|
newton,
|
|
42
71
|
pascal,
|
|
@@ -59,20 +88,40 @@ __all__ = [
|
|
|
59
88
|
"ampere",
|
|
60
89
|
"becquerel",
|
|
61
90
|
"candela",
|
|
91
|
+
"centimetre",
|
|
62
92
|
"coulomb",
|
|
63
93
|
"degree_celcius",
|
|
64
94
|
"farad",
|
|
95
|
+
"gram",
|
|
65
96
|
"gray",
|
|
66
97
|
"henry",
|
|
67
98
|
"hertz",
|
|
99
|
+
"hour",
|
|
68
100
|
"joule",
|
|
69
101
|
"katal",
|
|
70
102
|
"kelvin",
|
|
71
103
|
"kilogram",
|
|
104
|
+
"kiloampere",
|
|
105
|
+
"kilometre",
|
|
106
|
+
"kilovolt",
|
|
107
|
+
"kilowatt",
|
|
72
108
|
"lumen",
|
|
73
109
|
"lux",
|
|
110
|
+
"megawatt",
|
|
74
111
|
"metre",
|
|
112
|
+
"microgram",
|
|
113
|
+
"micrometre",
|
|
114
|
+
"microsecond",
|
|
115
|
+
"milliampere",
|
|
116
|
+
"milligram",
|
|
117
|
+
"millimetre",
|
|
118
|
+
"millisecond",
|
|
119
|
+
"millivolt",
|
|
120
|
+
"milliwatt",
|
|
121
|
+
"minute",
|
|
75
122
|
"mole",
|
|
123
|
+
"nanometre",
|
|
124
|
+
"nanosecond",
|
|
76
125
|
"newton",
|
|
77
126
|
"ohm",
|
|
78
127
|
"pascal",
|
|
@@ -82,6 +131,7 @@ __all__ = [
|
|
|
82
131
|
"sievert",
|
|
83
132
|
"steradian",
|
|
84
133
|
"tesla",
|
|
134
|
+
"tonne",
|
|
85
135
|
"volt",
|
|
86
136
|
"watt",
|
|
87
137
|
"weber",
|
|
@@ -11,12 +11,16 @@ from .quantity import (
|
|
|
11
11
|
Quantity,
|
|
12
12
|
complex_quantity,
|
|
13
13
|
complex_unit,
|
|
14
|
+
convert,
|
|
14
15
|
float_quantity,
|
|
15
16
|
float_unit,
|
|
16
17
|
int_quantity,
|
|
17
18
|
int_unit,
|
|
18
19
|
long_quantity,
|
|
19
20
|
long_unit,
|
|
21
|
+
multiplier,
|
|
22
|
+
unit,
|
|
23
|
+
value,
|
|
20
24
|
)
|
|
21
25
|
from .unit_definitions import BaseUnit, CustomUnitBase, DerivedUnit, SIUnit
|
|
22
26
|
|
|
@@ -33,10 +37,14 @@ __all__ = [
|
|
|
33
37
|
"UnitsError",
|
|
34
38
|
"complex_quantity",
|
|
35
39
|
"complex_unit",
|
|
40
|
+
"convert",
|
|
36
41
|
"float_quantity",
|
|
37
42
|
"float_unit",
|
|
38
43
|
"int_quantity",
|
|
39
44
|
"int_unit",
|
|
40
45
|
"long_quantity",
|
|
41
46
|
"long_unit",
|
|
47
|
+
"multiplier",
|
|
48
|
+
"unit",
|
|
49
|
+
"value",
|
|
42
50
|
]
|