Type hints
unitx ships inline annotations and py.typed. The parameter in Quantity[T]
or AffineQuantity[T] / LogQuantity[T] describes the magnitude type, not a physical dimension.
Units and compatible dimensions are validated at runtime.
Scalar construction and arithmetic
>>> from decimal import Decimal
>>> from unitx import Quantity
>>> from unitx.si import si
>>> q = Decimal('1.25') * si.m
>>> type(q.value) is Decimal
True
>>> (q / si.s).value is q.value
True
Constructing a Quantity or changing its unit product without numerical
arithmetic preserves T. Conversion and arithmetic can change the result type;
annotations use the corresponding result domain. unitx.math returns
Quantity[float] for supported Python real scalars and Quantity[Array]
for its array overloads. unitx.time clocks return AffineQuantity[int];
duration adapters use ordinary Quantity objects. time.from_datetime() returns
AffineQuantity[int]; time.to_datetime() returns an aware datetime.
unitx.serialization.from_dict() returns a union of the supported unit and
scalar quantity classes. Use isinstance to narrow it. The magnitude type is
int | float | Fraction | Decimal; the input record determines its runtime type.
See scalar data exchange.
A signature such as Quantity[float] does not require metres rather than
seconds. An Annotated label alone does not teach a type checker dimensional
algebra. unitx does not currently supply dimension-parameterized quantities
or a call-validation decorator.
Quantity subclasses
Preserving the magnitude parameter T does not preserve a Quantity subclass.
Quantity's ordinary binary arithmetic, unary operators and .to() construct
ordinary Quantity objects. A subclass must explicitly implement the operations
that preserve its own invariants. Scalar augmented operators delegate to the
corresponding binary operation; their annotations must describe the subclass's
results too.
For example, the Impedance extension
preserves its type for addition of two Impedances, parallel composition and
conversion. Adding an ordinary Quantity returns an ordinary Quantity in either
operand order. Its local overrides specialize the base class's wider input
domain; they are not a template for preserving every array or coordinate overload.
NumPy magnitude types
_numerics.py defines Scalar, Array, and Magnitude = Array | Scalar
without importing NumPy. Array is an input capability protocol, not a guarantee
of other backend support. Construction and unit-only operators preserve
Quantity[T], as does .T. Numerical array operations, conversions, indexing,
and iteration return Quantity[NDArray[Any] | numpy.number[Any]]; .flatten()
returns Quantity[NDArray[Any]]. .value therefore exposes native NumPy
methods such as reshape, astype, and sum. .dtype exposes a NumPy dtype.
Changed dtypes are not falsely annotated as the original dtype, and possible
zero-dimensional-array to NumPy-scalar results remain in the result union.
Boolean comparisons and isclose retain the NumPy-independent Array protocol.
Scalar is nonrecursive: its arithmetic methods return object at the protocol
boundary, while equality still returns bool. Concrete scalar and array result
types are described by Quantity's public overloads. Custom scalar magnitudes
must still return valid scalar results. This avoids recursively comparing
NumPy's many arithmetic overloads against Scalar's own return protocol.
_numpy_typing.py imports concrete NumPy types only for static analysis; core
runtime imports still do not load NumPy. Strict scalar-consumer checks also
run with NumPy absent. Checking the full library's optional NumPy annotations
requires NumPy; pyright tests/typing checks scalar consumers without it.
All checks share pyrightconfig.json; the test runner selects the input
directories and skips optional cases when their dependencies are unavailable.
NumPy's left-operand and function stubs do not generally describe every
unit-aware dispatch. With NumPy 2.5 stubs and Pyright 1.1.414, the documented
np.asarray([3000, 4000, 5000]) * si.K expression retains a Quantity type
without an explicit dtype. This path has strict regression coverage. NumPy
2.1 and 2.4 stubs can still infer Any here; runtime support remains NumPy >= 2.1.
np.array([...]) * unit can also become Any because its unknown-dtype array
matches NumPy's object-array multiplication fallback. unit * array or
Quantity(array, unit) provide alternatives across the supported versions.
Arbitrary shape/dtype inference is not promised, including the dtype NumPy
infers for unannotated input lists.
A direct np.expm1(q) works at runtime but NumPy's stubs may infer Any or ndarray
rather than Quantity for that intermediate result. The full example's final
Quantity types are checked independently; this does not imply that NumPy's
ufunc stubs preserve the wrapper at every intermediate call. For an explicit
numerical boundary, use np.expm1(np.asarray(q.value_in(si.one))) after
checking dimensions and normalizing the scale.
Use .value or .value_in(unit) at Matplotlib plotting boundaries too.
.to() returns a new Quantity; write q = q.to(unit) to retain the conversion.
It has no inplace option. NumPy-free scalar consumers retain working type hints.
For unitx.math array results, the NumPy-independent Array protocol avoids
unknown optional types in strict scalar users without NumPy. Use
np.asarray(result.value) when additional native ndarray methods are needed
in static analysis. Result dtype and shape remain determined by NumPy.
Verify a consumer
The test suite runs strict scalar fixtures with and without NumPy and checks
array consumers separately when NumPy is installed. tests/typing and
tests/numpy_typing are static fixtures excluded from pytest collection.
The small check_asarray.py regression requires NumPy >= 2.5 stubs; other array
fixtures cover the full supported NumPy range. Notebook checks extract the
actual code and add selected type assertions, rather than maintaining copies
of scientific examples. Runtime test annotations describe fixtures and test
data; they do not replace static assert_type() checks of public inference.
python -m pyright --pythonpath /path/to/your/python
Use an interpreter containing NumPy for full-library and array checks.
An installed-consumer check should run outside the checkout and confirm
that the wheel contains unitx/py.typed; source-tree inference alone does
not establish that annotations were packaged correctly.
Documentation index · Scalar numerical behavior · NumPy support