quantiphy 2.20__tar.gz → 2.22__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.
- quantiphy-2.22/LICENSE +21 -0
- {quantiphy-2.20 → quantiphy-2.22}/PKG-INFO +6 -5
- {quantiphy-2.20 → quantiphy-2.22}/README.rst +3 -3
- {quantiphy-2.20 → quantiphy-2.22}/pyproject.toml +11 -1
- {quantiphy-2.20 → quantiphy-2.22}/quantiphy/quantiphy.py +221 -100
- {quantiphy-2.20 → quantiphy-2.22}/quantiphy/quantiphy.pyi +1 -0
- {quantiphy-2.20 → quantiphy-2.22}/quantiphy/__init__.py +0 -0
- {quantiphy-2.20 → quantiphy-2.22}/quantiphy/__init__.pyi +0 -0
- {quantiphy-2.20 → quantiphy-2.22}/quantiphy/py.typed +0 -0
quantiphy-2.22/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016-2026 Kenneth S. Kundert
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: quantiphy
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.22
|
|
4
4
|
Summary: physical quantities (numbers with units)
|
|
5
5
|
Keywords: quantities,physical quantity,units,SI scale factors,engineering notation,mks,cgs
|
|
6
6
|
Author: Ken Kundert
|
|
@@ -16,6 +16,7 @@ Classifier: Operating System :: POSIX :: Linux
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Topic :: Utilities
|
|
18
18
|
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
License-File: LICENSE
|
|
19
20
|
Project-URL: changelog, https://github.com/KenKundert/quantiphy/blob/master/doc/releases.rst
|
|
20
21
|
Project-URL: documentation, https://quantiphy.readthedocs.io
|
|
21
22
|
Project-URL: homepage, https://quantiphy.readthedocs.io
|
|
@@ -27,8 +28,8 @@ QuantiPhy — Physical Quantities
|
|
|
27
28
|
|downloads| |build status| |coverage| |rtd status| |pypi version| |anaconda version| |python version|
|
|
28
29
|
|
|
29
30
|
| Author: Ken Kundert
|
|
30
|
-
| Version: 2.
|
|
31
|
-
| Released:
|
|
31
|
+
| Version: 2.22
|
|
32
|
+
| Released: 2026-06-27
|
|
32
33
|
|
|
|
33
34
|
|
|
34
35
|
|
|
@@ -102,7 +103,7 @@ Quick Start
|
|
|
102
103
|
You can find the documentation on `ReadTheDocs
|
|
103
104
|
<https://quantiphy.readthedocs.io>`_. Install with::
|
|
104
105
|
|
|
105
|
-
pip3 install
|
|
106
|
+
pip3 install quantiphy
|
|
106
107
|
|
|
107
108
|
Requires Python 3.6 or newer. If you using an earlier version of Python,
|
|
108
109
|
install version 2.10 of *QuantiPhy*.
|
|
@@ -4,8 +4,8 @@ QuantiPhy — Physical Quantities
|
|
|
4
4
|
|downloads| |build status| |coverage| |rtd status| |pypi version| |anaconda version| |python version|
|
|
5
5
|
|
|
6
6
|
| Author: Ken Kundert
|
|
7
|
-
| Version: 2.
|
|
8
|
-
| Released:
|
|
7
|
+
| Version: 2.22
|
|
8
|
+
| Released: 2026-06-27
|
|
9
9
|
|
|
|
10
10
|
|
|
11
11
|
|
|
@@ -79,7 +79,7 @@ Quick Start
|
|
|
79
79
|
You can find the documentation on `ReadTheDocs
|
|
80
80
|
<https://quantiphy.readthedocs.io>`_. Install with::
|
|
81
81
|
|
|
82
|
-
pip3 install
|
|
82
|
+
pip3 install quantiphy
|
|
83
83
|
|
|
84
84
|
Requires Python 3.6 or newer. If you using an earlier version of Python,
|
|
85
85
|
install version 2.10 of *QuantiPhy*.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "quantiphy"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.22"
|
|
4
4
|
description = "physical quantities (numbers with units)"
|
|
5
5
|
readme = "README.rst"
|
|
6
6
|
keywords = [
|
|
@@ -38,3 +38,13 @@ changelog = "https://github.com/KenKundert/quantiphy/blob/master/doc/releases.rs
|
|
|
38
38
|
[build-system]
|
|
39
39
|
requires = ["flit_core >=2,<4"]
|
|
40
40
|
build-backend = "flit_core.buildapi"
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
exclude = [".tox", "doc", "tests", ".diffs"]
|
|
44
|
+
|
|
45
|
+
[tool.ruff.lint]
|
|
46
|
+
select = ["F"]
|
|
47
|
+
ignore = []
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint.per-file-ignores]
|
|
50
|
+
"quantiphy/__init__.py" = ["F401"] # imported but unused
|
|
@@ -22,7 +22,7 @@ Documentation can be found at https://quantiphy.readthedocs.io.
|
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
# MIT License {{{1
|
|
25
|
-
# Copyright (C) 2016-
|
|
25
|
+
# Copyright (C) 2016-2026 Kenneth S. Kundert
|
|
26
26
|
#
|
|
27
27
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
28
|
# of this software and associated documentation files (the "Software"), to deal
|
|
@@ -73,21 +73,29 @@ def _scale(scale, unscaled):
|
|
|
73
73
|
if isinstance(scale, str):
|
|
74
74
|
scaled = UnitConversion._convert_units(scale, unscaled.units, unscaled)
|
|
75
75
|
to_units = scale
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
return scaled, to_units
|
|
77
|
+
|
|
78
|
+
if callable(scale):
|
|
78
79
|
try:
|
|
79
|
-
scaled, to_units = scale(unscaled
|
|
80
|
-
#
|
|
81
|
-
except TypeError:
|
|
82
|
-
# otherwise, assume it is a scale factor
|
|
80
|
+
scaled, to_units = scale(unscaled)
|
|
81
|
+
# do not pass units as second argument, this is the new style
|
|
82
|
+
except TypeError as e:
|
|
83
83
|
try:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
except TypeError:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
scaled, to_units = scale(unscaled, unscaled.units)
|
|
85
|
+
# passing units as second argument is redundant, deprecated
|
|
86
|
+
except TypeError: # pragma: no cover
|
|
87
|
+
raise e
|
|
88
|
+
return scaled, to_units
|
|
89
|
+
|
|
90
|
+
# otherwise, assume it is a scale factor
|
|
91
|
+
try:
|
|
92
|
+
# might be a tuple containing scale factor and units
|
|
93
|
+
multiplier, to_units = scale
|
|
94
|
+
except TypeError:
|
|
95
|
+
# otherwise, assume it is just a scale factor
|
|
96
|
+
multiplier = scale
|
|
97
|
+
to_units = unscaled.units
|
|
98
|
+
scaled = multiplier * unscaled
|
|
91
99
|
return scaled, to_units
|
|
92
100
|
|
|
93
101
|
|
|
@@ -367,8 +375,8 @@ def add_constant(value, alias=None, unit_systems=None):
|
|
|
367
375
|
|
|
368
376
|
|
|
369
377
|
# Globals {{{1
|
|
370
|
-
__version__ = '2.
|
|
371
|
-
__released__ = '
|
|
378
|
+
__version__ = '2.22'
|
|
379
|
+
__released__ = '2026-06-27'
|
|
372
380
|
|
|
373
381
|
# These mappings are only used when reading numbers
|
|
374
382
|
# The key for these mappings must be a single character
|
|
@@ -418,18 +426,26 @@ BINARY_MAPPINGS = {
|
|
|
418
426
|
# These mappings are only used when writing numbers
|
|
419
427
|
BIG_SCALE_FACTORS = 'kMGTPEZYRQ'
|
|
420
428
|
# These must be given in order, one for every three decades.
|
|
421
|
-
# Use k rather than K
|
|
429
|
+
# Use k rather than K because K looks like a temperature when used alone.
|
|
422
430
|
|
|
423
431
|
SMALL_SCALE_FACTORS = 'munpfazyrq'
|
|
424
432
|
# These must be given in order, one for every three decades.
|
|
425
433
|
|
|
426
|
-
# Supported currency symbols (these
|
|
427
|
-
CURRENCY_SYMBOLS = '
|
|
434
|
+
# Supported currency symbols (these precede the number)
|
|
435
|
+
CURRENCY_SYMBOLS = '$€¥£₩₺₽₹Ƀ₿฿Ξ'
|
|
436
|
+
|
|
437
|
+
# Units that abut the number.
|
|
438
|
+
# % is controversial, NIST and ISO say that a space should be used to separate
|
|
439
|
+
# the percent sign from a number, but the Chicago Manual of Style says the
|
|
440
|
+
# opposite.
|
|
441
|
+
TIGHT_UNITS = '''°'"′″'''
|
|
442
|
+
# The code is written assuming that TIGHT_UNITS includes only single
|
|
443
|
+
# character symbols, though the user can add multi-character tight units.
|
|
428
444
|
|
|
429
445
|
# Unit symbols that are not simple letters.
|
|
430
446
|
# Do not include % as it will be picked up when converting text to numbers,
|
|
431
447
|
# which is generally not desired (you would end up converting 0.001% to 1m%).
|
|
432
|
-
UNIT_SYMBOLS =
|
|
448
|
+
UNIT_SYMBOLS = """ÅΩƱΩ℧Δ¢ș""" + CURRENCY_SYMBOLS + TIGHT_UNITS
|
|
433
449
|
|
|
434
450
|
# Regular expression for recognizing and decomposing string .format method codes
|
|
435
451
|
FORMAT_SPEC = re.compile(r'''\A
|
|
@@ -483,6 +499,8 @@ DEFAULTS = dict(
|
|
|
483
499
|
output_sf = 'TGMkmunpfa',
|
|
484
500
|
plus = '+',
|
|
485
501
|
prec = 4,
|
|
502
|
+
preferred_quantities = {},
|
|
503
|
+
_preferred_quantities = {}, # transposed version of preferred_quantities
|
|
486
504
|
preferred_units = {},
|
|
487
505
|
_preferred_units = {}, # transposed version of preferred_units
|
|
488
506
|
radix = '.',
|
|
@@ -494,7 +512,7 @@ DEFAULTS = dict(
|
|
|
494
512
|
spacer = ' ',
|
|
495
513
|
strip_radix = True,
|
|
496
514
|
strip_zeros = True,
|
|
497
|
-
tight_units =
|
|
515
|
+
tight_units = list(TIGHT_UNITS),
|
|
498
516
|
unity_sf = '',
|
|
499
517
|
)
|
|
500
518
|
|
|
@@ -642,35 +660,54 @@ class Quantity(float):
|
|
|
642
660
|
|
|
643
661
|
# preferences {{{2
|
|
644
662
|
_initialized = False
|
|
663
|
+
transparent_preferences = False
|
|
664
|
+
# if set true, subclasses will use current preferences from the parent
|
|
665
|
+
# class, even those that have changed since the subclass was created.
|
|
645
666
|
|
|
646
667
|
# initialize preferences {{{3
|
|
647
668
|
@classmethod
|
|
648
669
|
def _initialize_preferences(cls):
|
|
649
|
-
if cls._initialized
|
|
650
|
-
|
|
651
|
-
cls.reset_prefs()
|
|
670
|
+
if cls._initialized != id(cls):
|
|
671
|
+
cls.reset_prefs()
|
|
652
672
|
|
|
653
673
|
# reset preferences {{{3
|
|
654
674
|
@classmethod
|
|
655
|
-
def reset_prefs(cls):
|
|
675
|
+
def reset_prefs(cls, transparent=None):
|
|
656
676
|
"""Reset preferences
|
|
657
677
|
|
|
678
|
+
:arg bool transparent:
|
|
679
|
+
If true this class inherits current preferences from the parent
|
|
680
|
+
classes. In false, the parents preferences are copied into this
|
|
681
|
+
class the first time it is used, and any changes made to the parents
|
|
682
|
+
preferences after first use are ignored. The default is false.
|
|
683
|
+
|
|
658
684
|
Resets all preferences to the current preferences of the parent class.
|
|
659
685
|
If there is no parent class, they are reset to their defaults.
|
|
660
686
|
"""
|
|
687
|
+
if transparent is None:
|
|
688
|
+
transparent = cls.transparent_preferences
|
|
689
|
+
|
|
661
690
|
cls._initialized = id(cls)
|
|
662
691
|
if cls == Quantity:
|
|
692
|
+
# this is the base class
|
|
663
693
|
prefs = DEFAULTS
|
|
694
|
+
transparent = False
|
|
664
695
|
else:
|
|
696
|
+
# this is a subclass
|
|
665
697
|
parent = cls.__mro__[1]
|
|
666
698
|
# for some reason I cannot get super to work right
|
|
667
699
|
prefs = parent._preferences
|
|
668
|
-
|
|
669
|
-
|
|
700
|
+
|
|
701
|
+
if not transparent:
|
|
702
|
+
# copy dict so subsequent changes made to parent's preferences do not affect us
|
|
703
|
+
prefs = dict(prefs)
|
|
704
|
+
|
|
705
|
+
cls.transparent_preferences = transparent
|
|
706
|
+
|
|
670
707
|
cls._preferences = ChainMap({}, prefs)
|
|
671
708
|
# use chain to support use of contexts
|
|
672
709
|
# put empty map in as first so user never accidentally deletes or
|
|
673
|
-
#
|
|
710
|
+
# modifies one of the initial preferences
|
|
674
711
|
|
|
675
712
|
# set preferences {{{3
|
|
676
713
|
@classmethod
|
|
@@ -926,12 +963,20 @@ class Quantity(float):
|
|
|
926
963
|
*QuantiPhy* currently does not add leading plus signs to either
|
|
927
964
|
mantissa or exponent, so this setting is ignored.
|
|
928
965
|
|
|
929
|
-
:arg
|
|
930
|
-
Default precision
|
|
931
|
-
be nonnegative. This precision is used when the
|
|
932
|
-
not required. Default is 4.
|
|
966
|
+
:arg prec:
|
|
967
|
+
Default precision in digits where 0 corresponds to 1 digit. Must
|
|
968
|
+
be a nonnegative integer or "full". This precision is used when the
|
|
969
|
+
full precision is not required. Default is 4.
|
|
933
970
|
:type prec: int or str
|
|
934
971
|
|
|
972
|
+
:arg dict preferred_quantities:
|
|
973
|
+
A dictionary that is used when looking up the preferred quantity when
|
|
974
|
+
instantiating. For example, if *preferred_quantities* contains the entry:
|
|
975
|
+
{Decibels: “dB dBV dBA dBm dBc”} where *Decibels* is a subclass of
|
|
976
|
+
*Quantity*, then when instantiating a quantity with units “dB”, “dBV”,
|
|
977
|
+
“dBA”, “dBm”, or “dBc”, the quantity returned would have *Decibels*
|
|
978
|
+
as its class.
|
|
979
|
+
|
|
935
980
|
:arg dict preferred_units:
|
|
936
981
|
A dictionary that is used when looking up the preferred units when
|
|
937
982
|
rendering. For example, if *preferred_units* contains the entry:
|
|
@@ -967,15 +1012,19 @@ class Quantity(float):
|
|
|
967
1012
|
|
|
968
1013
|
:type show_label: 'f', 'a', or bool
|
|
969
1014
|
|
|
1015
|
+
:arg bool show_units:
|
|
1016
|
+
Whether the units should be included when rendering a quantity to a
|
|
1017
|
+
string. By default *show_units* is True.
|
|
1018
|
+
|
|
970
1019
|
:arg str spacer:
|
|
971
1020
|
The spacer text to be inserted in a string between the numeric value
|
|
972
1021
|
and the scale factor when units are present. Is generally specified
|
|
973
1022
|
to be '' or ' '; use the latter if you prefer a space between the
|
|
974
1023
|
number and the units. Generally using ' ' makes numbers easier to
|
|
975
1024
|
read, particularly with complex units, and using '' is easier to
|
|
976
|
-
parse.
|
|
977
|
-
your convenience, you can access a
|
|
978
|
-
:attr:`Quantity.non_breaking_space`,
|
|
1025
|
+
parse. Use of a non-breaking space is preferred when embedding
|
|
1026
|
+
numbers in prose. For your convenience, you can access a
|
|
1027
|
+
non-breaking spaces using :attr:`Quantity.non_breaking_space`,
|
|
979
1028
|
:attr:`Quantity.narrow_non_breaking_space`, or
|
|
980
1029
|
:attr:`Quantity.thin_space`.
|
|
981
1030
|
|
|
@@ -1053,13 +1102,21 @@ class Quantity(float):
|
|
|
1053
1102
|
if isinstance(kwargs.get('known_units'), str):
|
|
1054
1103
|
kwargs['known_units'] = kwargs['known_units'].split()
|
|
1055
1104
|
|
|
1105
|
+
# split preferred_quantities
|
|
1106
|
+
if 'preferred_quantities' in kwargs:
|
|
1107
|
+
kwargs['_preferred_quantities'] = {
|
|
1108
|
+
unit : quantity
|
|
1109
|
+
for quantity, units in kwargs['preferred_quantities'].items()
|
|
1110
|
+
for unit in units.split()
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1056
1113
|
# split preferred_units
|
|
1057
1114
|
if 'preferred_units' in kwargs:
|
|
1058
|
-
_preferred_units = {
|
|
1059
|
-
|
|
1060
|
-
for
|
|
1061
|
-
|
|
1062
|
-
|
|
1115
|
+
kwargs['_preferred_units'] = {
|
|
1116
|
+
unit : preferred
|
|
1117
|
+
for preferred, units in kwargs['preferred_units'].items()
|
|
1118
|
+
for unit in units.split()
|
|
1119
|
+
}
|
|
1063
1120
|
|
|
1064
1121
|
# check for unknown output scale factors
|
|
1065
1122
|
if kwargs.get('output_sf'):
|
|
@@ -1119,6 +1176,7 @@ class Quantity(float):
|
|
|
1119
1176
|
|
|
1120
1177
|
"""
|
|
1121
1178
|
cls._initialize_preferences()
|
|
1179
|
+
|
|
1122
1180
|
try:
|
|
1123
1181
|
return getattr(cls, name, cls._preferences[name])
|
|
1124
1182
|
except KeyError:
|
|
@@ -1134,11 +1192,15 @@ class Quantity(float):
|
|
|
1134
1192
|
def __enter__(self):
|
|
1135
1193
|
cls = self.cls
|
|
1136
1194
|
cls._initialize_preferences()
|
|
1137
|
-
cls._preferences
|
|
1195
|
+
cls._preferences.maps.insert(0, {})
|
|
1196
|
+
# do not use ChainMap.new_child() as that creates a new ChainMap,
|
|
1197
|
+
# orphaning the original, which could be being used by a subclass
|
|
1138
1198
|
cls.set_prefs(**self.kwargs)
|
|
1139
1199
|
|
|
1140
1200
|
def __exit__(self, *args):
|
|
1141
|
-
self.cls._preferences
|
|
1201
|
+
self.cls._preferences.maps.pop(0)
|
|
1202
|
+
# do not use ChainMap.parents as that creates a new ChainMap,
|
|
1203
|
+
# orphaning the original, which could be being used by a subclass
|
|
1142
1204
|
|
|
1143
1205
|
# now, return the context manager
|
|
1144
1206
|
@classmethod
|
|
@@ -1518,7 +1580,7 @@ class Quantity(float):
|
|
|
1518
1580
|
)
|
|
1519
1581
|
)
|
|
1520
1582
|
cls.embedded_e_notation_only = re.compile(
|
|
1521
|
-
'{left_delimit}{sign}{mantissa}{exponent}{space}{smpl_units}
|
|
1583
|
+
r'{left_delimit}{sign}{mantissa}{exponent}{space}{smpl_units}\b'.format(
|
|
1522
1584
|
**locals()
|
|
1523
1585
|
)
|
|
1524
1586
|
)
|
|
@@ -1671,7 +1733,13 @@ class Quantity(float):
|
|
|
1671
1733
|
|
|
1672
1734
|
# create the underlying data structure and add attributes {{{3
|
|
1673
1735
|
try:
|
|
1674
|
-
|
|
1736
|
+
preferred_quantities = cls._preferences["_preferred_quantities"]
|
|
1737
|
+
preferred_quantity = preferred_quantities[units]
|
|
1738
|
+
assert issubclass(preferred_quantity, cls)
|
|
1739
|
+
except (AttributeError, KeyError):
|
|
1740
|
+
preferred_quantity = cls
|
|
1741
|
+
try:
|
|
1742
|
+
self = float.__new__(preferred_quantity, number)
|
|
1675
1743
|
except TypeError:
|
|
1676
1744
|
raise InvalidNumber(number)
|
|
1677
1745
|
if units:
|
|
@@ -1783,11 +1851,12 @@ class Quantity(float):
|
|
|
1783
1851
|
are taken to the be desired units and the behavior is the same as
|
|
1784
1852
|
if a string were given, except that *cls* defaults to the given
|
|
1785
1853
|
subclass.
|
|
1854
|
+
:type scale: real, pair, function, string, or quantity
|
|
1855
|
+
|
|
1786
1856
|
:arg class cls:
|
|
1787
1857
|
Class to use for return value. If not given, the class of self is
|
|
1788
1858
|
used it the units do not change, in which case :class:`Quantity` is
|
|
1789
1859
|
used.
|
|
1790
|
-
:type scale: real, pair, function, string, or quantity
|
|
1791
1860
|
|
|
1792
1861
|
:raises UnknownConversion(QuantiPhyError, KeyError):
|
|
1793
1862
|
A unit conversion was requested and there is no corresponding unit
|
|
@@ -1864,8 +1933,8 @@ class Quantity(float):
|
|
|
1864
1933
|
def render(
|
|
1865
1934
|
self,
|
|
1866
1935
|
*,
|
|
1867
|
-
form=None, show_units=None, prec=None, show_label=None,
|
|
1868
|
-
|
|
1936
|
+
form=None, show_units=None, prec=None, show_label=None, strip_zeros=None,
|
|
1937
|
+
strip_radix=None, spacer=None, scale=None, negligible=None
|
|
1869
1938
|
):
|
|
1870
1939
|
# description {{{3
|
|
1871
1940
|
"""Convert quantity to a string.
|
|
@@ -1980,6 +2049,7 @@ class Quantity(float):
|
|
|
1980
2049
|
show_units = self.show_units if show_units is None else show_units
|
|
1981
2050
|
strip_zeros = self.strip_zeros if strip_zeros is None else strip_zeros
|
|
1982
2051
|
strip_radix = self.strip_radix if strip_radix is None else strip_radix
|
|
2052
|
+
spacer = self.spacer if spacer is None else spacer
|
|
1983
2053
|
negligible = self.negligible if negligible is None else negligible
|
|
1984
2054
|
units = self._preferred_units.get(self.units, self.units) if show_units else ''
|
|
1985
2055
|
if prec is None:
|
|
@@ -2102,7 +2172,7 @@ class Quantity(float):
|
|
|
2102
2172
|
# combine mantissa, scale factor, and units and return the result {{{3
|
|
2103
2173
|
if sf_is_exp == 'unk':
|
|
2104
2174
|
sf_is_exp = (sf == eexp)
|
|
2105
|
-
value = self._combine(mantissa, sf, units,
|
|
2175
|
+
value = self._combine(mantissa, sf, units, spacer, sf_is_exp)
|
|
2106
2176
|
return self._label(value, show_label)
|
|
2107
2177
|
|
|
2108
2178
|
# fixed() {{{2
|
|
@@ -2110,7 +2180,7 @@ class Quantity(float):
|
|
|
2110
2180
|
self,
|
|
2111
2181
|
*,
|
|
2112
2182
|
show_units=None, prec=None, show_label=None, show_commas=None,
|
|
2113
|
-
strip_zeros=None, strip_radix=None, scale=None,
|
|
2183
|
+
strip_zeros=None, strip_radix=None, spacer=None, scale=None,
|
|
2114
2184
|
):
|
|
2115
2185
|
# description {{{3
|
|
2116
2186
|
"""Convert quantity to fixed-point string.
|
|
@@ -2212,6 +2282,7 @@ class Quantity(float):
|
|
|
2212
2282
|
show_commas = self.show_commas if show_commas is None else show_commas
|
|
2213
2283
|
strip_zeros = self.strip_zeros if strip_zeros is None else strip_zeros
|
|
2214
2284
|
strip_radix = self.strip_radix if strip_radix is None else strip_radix
|
|
2285
|
+
spacer = self.spacer if spacer is None else spacer
|
|
2215
2286
|
units = self._preferred_units.get(self.units, self.units) if show_units else ''
|
|
2216
2287
|
if prec is None:
|
|
2217
2288
|
prec = self.prec
|
|
@@ -2274,13 +2345,13 @@ class Quantity(float):
|
|
|
2274
2345
|
mantissa += '0'
|
|
2275
2346
|
|
|
2276
2347
|
# combine mantissa, scale factor and units and return result {{{3
|
|
2277
|
-
value = self._combine(mantissa, '', units,
|
|
2348
|
+
value = self._combine(mantissa, '', units, spacer)
|
|
2278
2349
|
return self._label(value, show_label)
|
|
2279
2350
|
|
|
2280
2351
|
# binary() {{{2
|
|
2281
2352
|
def binary(
|
|
2282
2353
|
self, *, show_units=None, prec=None, show_label=None,
|
|
2283
|
-
strip_zeros=None, strip_radix=None, scale=None,
|
|
2354
|
+
strip_zeros=None, strip_radix=None, spacer=None, scale=None,
|
|
2284
2355
|
):
|
|
2285
2356
|
# description {{{3
|
|
2286
2357
|
"""Convert quantity to string using binary scale factors.
|
|
@@ -2365,6 +2436,7 @@ class Quantity(float):
|
|
|
2365
2436
|
show_units = self.show_units if show_units is None else show_units
|
|
2366
2437
|
strip_zeros = self.strip_zeros if strip_zeros is None else strip_zeros
|
|
2367
2438
|
strip_radix = self.strip_radix if strip_radix is None else strip_radix
|
|
2439
|
+
spacer = self.spacer if spacer is None else spacer
|
|
2368
2440
|
units = self._preferred_units.get(self.units, self.units) if show_units else ''
|
|
2369
2441
|
if prec is None:
|
|
2370
2442
|
prec = self.prec
|
|
@@ -2430,7 +2502,7 @@ class Quantity(float):
|
|
|
2430
2502
|
mantissa += '0'
|
|
2431
2503
|
|
|
2432
2504
|
# combine mantissa, scale factor and units and return result {{{3
|
|
2433
|
-
value = self._combine(mantissa, sf, units,
|
|
2505
|
+
value = self._combine(mantissa, sf, units, spacer, sf_is_exp)
|
|
2434
2506
|
return self._label(value, show_label)
|
|
2435
2507
|
|
|
2436
2508
|
# is_close() {{{2
|
|
@@ -2572,11 +2644,11 @@ class Quantity(float):
|
|
|
2572
2644
|
# code {{{3
|
|
2573
2645
|
match = FORMAT_SPEC.match(template)
|
|
2574
2646
|
if match:
|
|
2575
|
-
align,
|
|
2647
|
+
align, use_alt_form, width, comma, prec, ftype, units = match.groups()
|
|
2576
2648
|
scale = units if units else None
|
|
2577
2649
|
prec = int(prec) if prec else None
|
|
2578
2650
|
ftype = ftype if ftype else ''
|
|
2579
|
-
alt_form = dict(strip_zeros=False, strip_radix=False) if
|
|
2651
|
+
alt_form = dict(strip_zeros=False, strip_radix=False) if use_alt_form else {}
|
|
2580
2652
|
if ftype and ftype in 'dnu':
|
|
2581
2653
|
if ftype == 'u':
|
|
2582
2654
|
value = scale if scale else self.units
|
|
@@ -2629,12 +2701,12 @@ class Quantity(float):
|
|
|
2629
2701
|
))
|
|
2630
2702
|
else:
|
|
2631
2703
|
value = float(self)
|
|
2632
|
-
value = '{0:{1}.{2}{3}}'.format(value, comma, prec, ftype)
|
|
2704
|
+
value = '{0:{4}{1}.{2}{3}}'.format(value, comma, prec, ftype, use_alt_form)
|
|
2633
2705
|
value = self._map_leading_sign(value)
|
|
2634
2706
|
value = self._map_sign(value)
|
|
2635
2707
|
width = width.lstrip('0')
|
|
2636
2708
|
# format function treats 0 as a padding rather than a width
|
|
2637
|
-
if self.strip_zeros:
|
|
2709
|
+
if alt_form.get("strip_zeros", self.strip_zeros):
|
|
2638
2710
|
if 'e' in value:
|
|
2639
2711
|
mantissa, exponent = value.split('e')
|
|
2640
2712
|
if '.' in mantissa:
|
|
@@ -2923,7 +2995,8 @@ class Quantity(float):
|
|
|
2923
2995
|
end = match.start(0)
|
|
2924
2996
|
number = match.group(0)
|
|
2925
2997
|
try:
|
|
2926
|
-
|
|
2998
|
+
q = cls.__new__(cls, number)
|
|
2999
|
+
number = q.render(**kwargs)
|
|
2927
3000
|
except ValueError: # pragma: no cover
|
|
2928
3001
|
# something unexpected happened
|
|
2929
3002
|
# but this is not essential, so ignore it
|
|
@@ -2972,7 +3045,8 @@ class Quantity(float):
|
|
|
2972
3045
|
end = match.start(0)
|
|
2973
3046
|
number = match.group(0)
|
|
2974
3047
|
try:
|
|
2975
|
-
|
|
3048
|
+
q = cls.__new__(cls, number)
|
|
3049
|
+
number = q.render(**kwargs)
|
|
2976
3050
|
except ValueError: # pragma: no cover
|
|
2977
3051
|
# something unexpected happened
|
|
2978
3052
|
# but this is not essential, so ignore it
|
|
@@ -3130,9 +3204,6 @@ add_constant(
|
|
|
3130
3204
|
# Unit Conversions {{{1
|
|
3131
3205
|
# UnitConversion class {{{2
|
|
3132
3206
|
class UnitConversion(object):
|
|
3133
|
-
_unit_conversions = {}
|
|
3134
|
-
_known_units = set()
|
|
3135
|
-
|
|
3136
3207
|
# description {{{3
|
|
3137
3208
|
"""
|
|
3138
3209
|
Creates a unit converter.
|
|
@@ -3271,6 +3342,11 @@ class UnitConversion(object):
|
|
|
3271
3342
|
|
|
3272
3343
|
"""
|
|
3273
3344
|
|
|
3345
|
+
_unit_conversions = {}
|
|
3346
|
+
_known_units = set()
|
|
3347
|
+
_support_si_sf_scaling = True
|
|
3348
|
+
_support_bin_sf_scaling = True
|
|
3349
|
+
|
|
3274
3350
|
# constructor {{{3
|
|
3275
3351
|
def __init__(self, to_units, from_units, slope=1, intercept=0):
|
|
3276
3352
|
self.slope = slope
|
|
@@ -3450,6 +3526,26 @@ class UnitConversion(object):
|
|
|
3450
3526
|
cls._unit_conversions = {}
|
|
3451
3527
|
cls._known_units = set()
|
|
3452
3528
|
|
|
3529
|
+
@classmethod
|
|
3530
|
+
def enable_sf_scaling(cls, si_scaling=None, bin_scaling=None):
|
|
3531
|
+
"""By default the given or desired units in a unit conversion or scaling
|
|
3532
|
+
may include scale factors. This is true for both SI and binary scale
|
|
3533
|
+
factors. The scale factor is provided as a prefix on the units. In
|
|
3534
|
+
rare cases the acceptance of scale factors may create problems. You can
|
|
3535
|
+
use this method to disable support for interpreting scale factors in
|
|
3536
|
+
unit conversions.
|
|
3537
|
+
|
|
3538
|
+
si_scaling (bool):
|
|
3539
|
+
Enables or disables support for SI scale factor scaling.
|
|
3540
|
+
|
|
3541
|
+
bin_scaling (bool):
|
|
3542
|
+
Enables or disables support for binary scale factor scaling.
|
|
3543
|
+
"""
|
|
3544
|
+
if si_scaling is not None:
|
|
3545
|
+
cls._support_si_sf_scaling = si_scaling
|
|
3546
|
+
if bin_scaling is not None:
|
|
3547
|
+
cls._support_bin_sf_scaling = bin_scaling
|
|
3548
|
+
|
|
3453
3549
|
# fixture() {{{3
|
|
3454
3550
|
@staticmethod
|
|
3455
3551
|
def fixture(converter_func):
|
|
@@ -3554,6 +3650,8 @@ class UnitConversion(object):
|
|
|
3554
3650
|
# If you want this functionality, simply use:
|
|
3555
3651
|
# Quantity(value, from_units).scale(to_units)
|
|
3556
3652
|
|
|
3653
|
+
orig_to_units, orig_from_units = to_units, from_units
|
|
3654
|
+
|
|
3557
3655
|
def get_converter(to_units, from_units):
|
|
3558
3656
|
# handle unity scale factor conversions
|
|
3559
3657
|
if (
|
|
@@ -3571,45 +3669,43 @@ class UnitConversion(object):
|
|
|
3571
3669
|
# b. there is no scale factor on the from_units
|
|
3572
3670
|
# c. there are scale factors on both the to_ and from_units
|
|
3573
3671
|
|
|
3672
|
+
# separate scale factor from units
|
|
3673
|
+
def extract_sf(units):
|
|
3674
|
+
# check for binary scale factor, all of which are two characters
|
|
3675
|
+
if cls._support_bin_sf_scaling:
|
|
3676
|
+
sf, unit = units[:2], units[2:]
|
|
3677
|
+
if sf in BINARY_MAPPINGS:
|
|
3678
|
+
return sf, unit, BINARY_MAPPINGS[sf]
|
|
3679
|
+
|
|
3680
|
+
# check for SI scale factor, all of which are 1 character
|
|
3681
|
+
if cls._support_si_sf_scaling:
|
|
3682
|
+
sf, unit = units[:1], units[1:]
|
|
3683
|
+
if sf in MAPPINGS:
|
|
3684
|
+
return sf, unit, float('1' + MAPPINGS[sf])
|
|
3685
|
+
|
|
3686
|
+
return None, units, 1
|
|
3687
|
+
|
|
3688
|
+
# separate scale factor from units
|
|
3574
3689
|
# handle known-unit cases for to_units
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
to_resolved = to_prefix in ALL_SF and to_suffix in cls._known_units
|
|
3580
|
-
if to_resolved:
|
|
3581
|
-
to_sf, to_units = to_prefix, to_suffix # case 2
|
|
3690
|
+
if to_units in cls._known_units: # case 1
|
|
3691
|
+
to_sf, to_scale = None, 1
|
|
3692
|
+
else: # case 2 or 3
|
|
3693
|
+
to_sf, to_units, to_scale = extract_sf(to_units)
|
|
3582
3694
|
|
|
3583
3695
|
# handle known-unit cases for from_units
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
from_resolved = (
|
|
3589
|
-
from_prefix in ALL_SF and from_suffix in cls._known_units
|
|
3590
|
-
)
|
|
3591
|
-
if from_resolved:
|
|
3592
|
-
from_sf, from_units = from_prefix, from_suffix # case 2
|
|
3593
|
-
|
|
3594
|
-
# handle same-unit cases
|
|
3595
|
-
if not to_resolved and not from_resolved: # case 3
|
|
3596
|
-
if to_units == from_suffix and from_prefix in ALL_SF: # case 3a
|
|
3597
|
-
from_sf, from_units = from_prefix, from_suffix
|
|
3598
|
-
elif from_units == to_suffix and to_prefix in ALL_SF: # case 3b
|
|
3599
|
-
to_sf, to_units = to_prefix, to_suffix
|
|
3600
|
-
elif from_prefix in ALL_SF and to_prefix in ALL_SF: # case 3c
|
|
3601
|
-
to_sf, to_units = to_prefix, to_suffix
|
|
3602
|
-
from_sf, from_units = from_prefix, from_suffix
|
|
3696
|
+
if from_units in cls._known_units: # case 1
|
|
3697
|
+
from_sf, from_scale = None, 1
|
|
3698
|
+
else: # case 2 or 3
|
|
3699
|
+
from_sf, from_units, from_scale = extract_sf(from_units)
|
|
3603
3700
|
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
return float('1' + MAPPINGS[sf])
|
|
3701
|
+
# handle unknown unit cases (to- and from- must have same units)
|
|
3702
|
+
if to_units == from_units: # case 3
|
|
3703
|
+
return to_units, from_units, to_scale, from_scale
|
|
3608
3704
|
|
|
3609
|
-
if
|
|
3610
|
-
return to_units, from_units,
|
|
3705
|
+
if to_units in cls._known_units or from_units in cls._known_units: # case 2
|
|
3706
|
+
return to_units, from_units, to_scale, from_scale
|
|
3611
3707
|
|
|
3612
|
-
raise UnknownConversion(to_units=
|
|
3708
|
+
raise UnknownConversion(to_units=orig_to_units, from_units=orig_from_units)
|
|
3613
3709
|
|
|
3614
3710
|
to_units, from_units, to_sf, from_sf = get_converter(to_units, from_units)
|
|
3615
3711
|
|
|
@@ -3618,8 +3714,29 @@ class UnitConversion(object):
|
|
|
3618
3714
|
value = Quantity(value, from_units)
|
|
3619
3715
|
if to_units == from_units:
|
|
3620
3716
|
return from_sf * value / to_sf
|
|
3621
|
-
|
|
3622
|
-
|
|
3717
|
+
try:
|
|
3718
|
+
converter = cls._unit_conversions[(to_units, from_units)]
|
|
3719
|
+
except KeyError:
|
|
3720
|
+
raise UnknownConversion(to_units=orig_to_units, from_units=orig_from_units)
|
|
3721
|
+
scaled = value.scale(from_sf)
|
|
3722
|
+
try:
|
|
3723
|
+
value = converter(scaled)
|
|
3724
|
+
except TypeError as e:
|
|
3725
|
+
# assume that this scale function is the deprecated form that
|
|
3726
|
+
# expects units as the second argument
|
|
3727
|
+
try:
|
|
3728
|
+
value = converter(scaled, scaled.units)
|
|
3729
|
+
except TypeError: # pragma: no cover
|
|
3730
|
+
raise e
|
|
3731
|
+
try:
|
|
3732
|
+
value, units = value
|
|
3733
|
+
# the Quantity.scale() method returns the units along with the
|
|
3734
|
+
# scaled value. This differs from UnitConversion scale
|
|
3735
|
+
# functions, which do not return the units. This code allows
|
|
3736
|
+
# UnitConversion to work with Quantity.scale() scale functions.
|
|
3737
|
+
except TypeError:
|
|
3738
|
+
pass
|
|
3739
|
+
return value / to_sf
|
|
3623
3740
|
|
|
3624
3741
|
|
|
3625
3742
|
# __str__ {{{3
|
|
@@ -3649,7 +3766,7 @@ UnitConversion('K', 'F °F', 5/9, 273.15 - 32*5/9)
|
|
|
3649
3766
|
UnitConversion('K', 'R °R', 5/9, 0)
|
|
3650
3767
|
|
|
3651
3768
|
# Length/Distance conversions {{{2
|
|
3652
|
-
UnitConversion('m', 'micron', 1/1000000)
|
|
3769
|
+
UnitConversion('m', 'micron microns', 1/1000000)
|
|
3653
3770
|
UnitConversion('m', 'Å angstrom', 1/10000000000)
|
|
3654
3771
|
UnitConversion('m', 'mi mile miles', 1609.344)
|
|
3655
3772
|
UnitConversion('m', 'ft feet', 0.3048)
|
|
@@ -3666,10 +3783,12 @@ UnitConversion('s', 'hr hour hours', 3600)
|
|
|
3666
3783
|
UnitConversion('s', 'day days', 86400)
|
|
3667
3784
|
|
|
3668
3785
|
# Bit conversions {{{2
|
|
3669
|
-
UnitConversion('b', 'B', 8)
|
|
3786
|
+
UnitConversion('b bit bits', 'B byte bytes', 8)
|
|
3787
|
+
UnitConversion('bps b/s', 'Bps B/s', 8)
|
|
3670
3788
|
|
|
3671
|
-
#
|
|
3672
|
-
UnitConversion(['
|
|
3789
|
+
# Currency conversions {{{2
|
|
3790
|
+
UnitConversion(['$'], ['USD'], 1)
|
|
3791
|
+
UnitConversion(['sat', 'sats', 'ș'], ['BTC', 'btc', 'Ƀ', '₿', '฿'], 1e8)
|
|
3673
3792
|
|
|
3674
3793
|
|
|
3675
3794
|
# Quantity functions {{{1
|
|
@@ -3771,3 +3890,5 @@ def binary(value, units, params=None, *args, **kwargs):
|
|
|
3771
3890
|
|
|
3772
3891
|
"""
|
|
3773
3892
|
return Quantity(value, units=units, params=params).binary(*args, **kwargs)
|
|
3893
|
+
|
|
3894
|
+
# vim: set sw=4 sts=4 tw=80 fo=ntcqwa12 et spell:
|
|
@@ -119,6 +119,7 @@ class Quantity(float):
|
|
|
119
119
|
show_label: bool | str = ...,
|
|
120
120
|
strip_zeros: bool = ...,
|
|
121
121
|
strip_radix: bool = ...,
|
|
122
|
+
spacer: str | bool = ...,
|
|
122
123
|
scale: str | float | tuple[float | Quantity, str] | Callable = ...,
|
|
123
124
|
negligible: float = ...,
|
|
124
125
|
) -> str: ...
|
|
File without changes
|
|
File without changes
|
|
File without changes
|