"I would love to address these questions publicly because the snippets used could be very misleading.
Source - https://stackoverflow.com/q/79917832
Posted by Rodrigo Torres
Retrieved 2026-03-31, License - CC BY-SA 4.0
# in types.py
type Scalar = bool | int | str |float
SCALAR_TYPES = (bool, int, str, float)
Yes, it works, but only part of what you’re doing is actually “real” typing, and that distinction matters.
Here is a clean breakdown on your snippest
What's really solid and correct
This is fully valid and aligns with modern typing (PEP 695 style):
type Scalar = bool | int | str | float`
def extract_scalar[T: Scalar](data: dict, key: str, expected: type[T]) -> T: ...
Kindly note that Scalar is a proper type alias and T: Scalar is a bound
That is my recommended approach
Let look at What works, but is a bit misleading
SCALAR_TYPES = (bool, int, str, float)
def extract_scalar[T: SCALAR_TYPES](...) -> T: ...
Yes, this runs. Yes, type checkers may accept it.
But here’s the catch:
SCALAR_TYPES is just a runtime tuple
It is not a type-level construct
PEP 695 expects literal tuples for constraints, not variables
So even though Python 3.14 (with lazy annotations from PEP 649) allows it syntactically, you’re relying on behavior that is:
not guaranteed by the typing spec
potentially inconsistent across tools
easy to break in future updates
So, In plain terms: you’re mixing runtime values with type system intent
Practical advice (what you should actually do)
You’re thinking in the right direction—reuse and centralization is good. Just split responsibilities cleanly:
# types.py
type Scalar = bool | int | str | float
SCALAR_TYPES = (bool, int, str, float)
Then:
# typing side
def extract_scalar[T: Scalar](...) -> T: ...
# runtime validation side
if not isinstance(value, SCALAR_TYPES):
...
Kindly use: type aliases for typing and tuples for runtime checks
Don’t try to make one do both jobs.
Top comments (0)