The docs mention that it makes sense to write Quantity[int] (or Quantity[np.ndarray[<shape>, <integer dtype>]]).
However, I think this poses some problems, because then a lot of operations can break this type invariance: for example, Quantity(3, 'cm').to_root_units() returns Quantity(0.03, "meter").
The real problem comes when we consider inplace operations:
def foo(x: Quantity[int]):
reveal_type(x.magnitude) # int (good)
x.ito_root_units()
reveal_type(x.magnitude) # type checker thinks it's int, but might be float at runtime
I don't think there's any way of telling type checkers that an in-place operation might've changed the type of a variable.
Is this expected behaviour? Should the docs be updated with a note about this?
Note: since type checkers think int <: float (and it's still an open problem), as long as MagnitudeT (in Quantity's definition) remains covariant (which simplifies a lot arithmetic method overloads) it's not possible to make the Quantity[int] annotation an error – the best we can do is document it.
However, in the case of numpy arrays, integer and floating dtypes are not in a subtype-supertype relationship, which means we can simply accept floating and complex arrays while the boolean/integer ones will be disallowed by default in the annotations.
Edit: apparently x.ito_root_units() fails at runtime if the magnitude is an integer array. The error is:
numpy._core._exceptions._UFuncOutputCastingError: Cannot cast ufunc 'multiply' output from dtype('float64') to dtype('int64') with casting rule 'same_kind'.
This happens because we're essentially doing self.magnitude *= <float> and numpy correctly raises because this operation cannot preserve the LHS type.
However, x.to_root_units() works correctly and returns a float array (following the data type promotion rules).
The docs mention that it makes sense to write
Quantity[int](orQuantity[np.ndarray[<shape>, <integer dtype>]]).However, I think this poses some problems, because then a lot of operations can break this type invariance: for example,
Quantity(3, 'cm').to_root_units()returnsQuantity(0.03, "meter").The real problem comes when we consider inplace operations:
I don't think there's any way of telling type checkers that an in-place operation might've changed the type of a variable.
Is this expected behaviour? Should the docs be updated with a note about this?
Note: since type checkers think
int <: float(and it's still an open problem), as long asMagnitudeT(inQuantity's definition) remains covariant (which simplifies a lot arithmetic method overloads) it's not possible to make theQuantity[int]annotation an error – the best we can do is document it.However, in the case of numpy arrays, integer and floating dtypes are not in a subtype-supertype relationship, which means we can simply accept
floatingandcomplexarrays while the boolean/integer ones will be disallowed by default in the annotations.Edit: apparently
x.ito_root_units()fails at runtime if the magnitude is an integer array. The error is:numpy._core._exceptions._UFuncOutputCastingError: Cannot cast ufunc 'multiply' output from dtype('float64') to dtype('int64') with casting rule 'same_kind'.This happens because we're essentially doing
self.magnitude *= <float>and numpy correctly raises because this operation cannot preserve the LHS type.However,
x.to_root_units()works correctly and returns a float array (following the data type promotion rules).