You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[PEP 484](https://peps.python.org/pep-0484/)'s [numeric
tower](https://peps.python.org/pep-0484/#the-numeric-tower) makes
**`int` assignable to any `float`-annotated parameter or field**, while
**at runtime `isinstance(1, float)` is `False`**.
Any field annotated `float` can therefore silently store an `int`, and
code unwrapping it with `match … case float():` falls through to
`assert_never()`, calls to `float`-only methods like `hex()` crash, and
`type()`-based dispatch misbehaves — despite everything type-checking
cleanly.
There is no clean fix in current Python (see the discussion in #250):
runtime coercion at ingress was prototyped and measured ~2.3× slower on
hot-path types, structural `Protocol` tricks don't close the widened
variable and `Sequence` covariance holes, and narrowing match arms one
by one leaves the annotation lying. So the decision is to stop lying
instead: annotate every such value as `float | int`, which is what PEP
484 actually admits, at zero runtime cost. It is also forward-compatible
with the typing-council proposal to make `float` mean `float | int`
(python/typing-council#46).
This commit replaces all `float` annotations with `FloatInt`, a type
alias for `float | int`.
Fixes#250.
Copy file name to clipboardExpand all lines: RELEASE_NOTES.md
+13Lines changed: 13 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -70,6 +70,14 @@
70
70
71
71
Malformed bounds are now preserved in the returned `MetricSample.bounds_set` as an `InvalidBoundsSet` (validity is encoded in the type), so the previous "bounds for ... is invalid, ignoring these bounds" major issue is no longer produced.
72
72
73
+
* `float`-typed fields and accessors are now annotated with the new `FloatInt` (`float | int`) type alias (see New Features), to be honest about what PEP 484's numeric tower actually admits. These symbols are affected:
74
+
75
+
* `frequenz.client.common.metrics.AggregatedMetricValue`: the `avg`, `min`, `max` and `raw` fields.
76
+
* `frequenz.client.common.metrics.MetricSample`: the `value` field and the `as_single_value()` return type.
77
+
* `frequenz.client.common.metrics.Bounds`: the `lower` and `upper` fields (shared with the new `BaseBounds` / `InvalidBounds` hierarchy).
78
+
79
+
Runtime behavior is completely unchanged: these fields could always end up storing `int` values (`x: float = 1` is legal even under `mypy --strict`), the annotations just didn't admit it. Reads that assign to `float`-typed destinations or do plain arithmetic keep type-checking as before. However, code that pattern-matches these values with a bare `case float():` arm — a latent runtime crash, since `isinstance(1, float)` is `False` — will now be flagged as non-exhaustive by strict type checkers and should be widened to `case float() | int():`, and calling `float`-only methods (e.g. `hex()`) on them now requires an explicit `float(...)` conversion.
80
+
73
81
## New Features
74
82
75
83
* Added 4 new electrical component classes for categories that previously collapsed into `UnrecognizedElectricalComponent`:
@@ -138,6 +146,11 @@
138
146
139
147
* Added a new `frequenz.client.common.microgrid.Microgrid` type with a raising `is_active()` method, together with the `frequenz.client.common.microgrid.proto.v1alpha8.microgrid_from_proto` conversion function.
140
148
149
+
* Added `frequenz.client.common.FloatInt`, a type alias for `float | int`.
150
+
151
+
PEP 484's numeric tower makes `int` assignable wherever `float` is annotated, even under `mypy --strict`, while at runtime `isinstance(1, float)` is `False` — so a plain `float` annotation silently admits values that crash `match … case float():` arms and `float`-only methods like `hex()`. The library now spells such annotations `FloatInt` instead of lying (see Upgrading); the alias docstring documents the trap in detail, including the inherent `bool ⊂ int` leak. New numeric fields (`Location` latitudes/longitudes, `PowerTransformer` voltages, bounds and bounds sets) use it as well. Values loaded from protobuf are unaffected in practice, as the wire always delivers real `float`s.
152
+
141
153
## Bug Fixes
142
154
143
155
* Fixed `EnumParityTest` so protobuf values whose Python member name exists with a different number fail parity checks instead of being treated as unmirrored protobuf values.
156
+
* Fixed potential unexpected exceptions due to type-checking accepting `int` for code annotated to only accept `float`. Fixes #250.
0 commit comments