DEV Community

SYLVESTER BENJAMIN
SYLVESTER BENJAMIN

Posted on

Can type bounds/constraints be stored in global variables and later used in generic function definitions with Python 3.14+?

"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)

Enter fullscreen mode Exit fullscreen mode

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: ...
Enter fullscreen mode Exit fullscreen mode

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: ...
Enter fullscreen mode Exit fullscreen mode

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)

Enter fullscreen mode Exit fullscreen mode

Then:

# typing side
def extract_scalar[T: Scalar](...) -> T: ...

# runtime validation side
if not isinstance(value, SCALAR_TYPES):
    ...
Enter fullscreen mode Exit fullscreen mode

Kindly use: type aliases for typing and tuples for runtime checks

Don’t try to make one do both jobs.

Top comments (0)